/*
 * Copyright (C) 2015-2017 Alibaba Group Holding Limited
 */

/*
 / _____)             _              | |
( (____  _____ ____ _| |_ _____  ____| |__
 \____ \| ___ |    (_   _) ___ |/ ___)  _ \
 _____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
    (C)2013 Semtech
 ___ _____ _   ___ _  _____ ___  ___  ___ ___
/ __|_   _/_\ / __| |/ / __/ _ \| _ \/ __| __|
\__ \ | |/ _ \ (__| ' <| _| (_) |   / (__| _|
|___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___|
embedded.connectivity.solutions===============

Description: LoRa MAC layer implementation

License: Revised BSD License, see LICENSE.TXT file include in the project

Maintainer: Miguel Luis ( Semtech ), Gregory Cristian ( Semtech ) and Daniel Jaeckle ( STACKFORCE )
*/
#include "LoRaMac.h"
#include "LoRaMacClassB.h"
#include "LoRaMacCrypto.h"
#include "log.h"
#include "radio.h"
#include "region/Region.h"
#include "timer.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#ifdef CONFIG_LWAN
#    include "lwan_config.h"
#endif
#include "LoRaMacConfirmQueue.h"
#include "LoRaMacTest.h"

/*!
 * Maximum PHY layer payload size
 */
#define LORAMAC_PHY_MAXPAYLOAD 255

/*!
 * Maximum MAC commands buffer size
 */
#define LORA_MAC_COMMAND_MAX_LENGTH 128

/*!
 * Maximum length of the fOpts field
 */
#define LORA_MAC_COMMAND_MAX_FOPTS_LENGTH 15

/*!
 * LoRaMac region.
 */
static LoRaMacRegion_t LoRaMacRegion;

/*!
 * LoRaMac duty cycle for the back-off procedure during the first hour.
 */
#define BACKOFF_DC_1_HOUR 100

/*!
 * LoRaMac duty cycle for the back-off procedure during the next 10 hours.
 */
#define BACKOFF_DC_10_HOURS 1000

/*!
 * LoRaMac duty cycle for the back-off procedure during the next 24 hours.
 */
#define BACKOFF_DC_24_HOURS 10000

#ifdef CONFIG_LORA_CAD
#    define LORA_CAD_CNT_MAX 8  // send frame after LORA_CAD_CNT_MAX times CAD
#    define LORA_CAD_SYMBOLS 8  // CAD symbols
#    define LORA_CAD_DELAY 1    // delay 1ms after CAD
#endif

/*!
 * Device IEEE EUI
 */
static uint8_t* LoRaMacDevEui;

/*!
 * Application IEEE EUI
 */
static uint8_t* LoRaMacAppEui;

/*!
 * AES encryption/decryption cipher application key
 */
static uint8_t* LoRaMacAppKey;

/*!
 * AES encryption/decryption cipher network session key
 */
static uint8_t LoRaMacNwkSKey[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

/*!
 * AES encryption/decryption cipher application session key
 */
static uint8_t LoRaMacAppSKey[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

/*!
 * Device nonce is a random value extracted by issuing a sequence of RSSI
 * measurements
 */
static uint16_t LoRaMacDevNonce;

/*!
 * Network ID ( 3 bytes )
 */
static uint32_t LoRaMacNetID;

/*!
 * Mote Address
 */
static uint32_t LoRaMacDevAddr;

/*!
 * Multicast channels linked list
 */
static MulticastParams_t* MulticastChannels = NULL;

/*!
 * Actual device class
 */
static DeviceClass_t LoRaMacDeviceClass;

/*!
 * Indicates if the node is connected to a private or public network
 */
static bool PublicNetwork;

/*!
 * Buffer containing the data to be sent or received.
 */
static uint8_t LoRaMacBuffer[LORAMAC_PHY_MAXPAYLOAD];

/*!
 * Length of packet in LoRaMacBuffer
 */
static uint16_t LoRaMacBufferPktLen = 0;

/*!
 * Length of the payload in LoRaMacBuffer
 */
static uint8_t LoRaMacTxPayloadLen = 0;

/*!
 * Buffer containing the upper layer data.
 */
static uint8_t LoRaMacRxPayload[LORAMAC_PHY_MAXPAYLOAD];

/*!
 * LoRaMAC frame counter. Each time a packet is sent the counter is incremented.
 * Only the 16 LSB bits are sent
 */
static uint32_t UpLinkCounter = 0;

/*!
 * LoRaMAC frame counter. Each time a packet is received the counter is incremented.
 * Only the 16 LSB bits are received
 */
static uint32_t DownLinkCounter = 0;

/*!
 * IsPacketCounterFixed enables the MIC field tests by fixing the
 * UpLinkCounter value
 */
static bool IsUpLinkCounterFixed = false;

/*!
 * Used for test purposes. Disables the opening of the reception windows.
 */
static bool IsRxWindowsEnabled = true;

/*!
 * Indicates if the MAC layer has already joined a network.
 */
static bool IsLoRaMacNetworkJoined = false;

/*!
 * LoRaMac ADR control status
 */
static bool AdrCtrlOn = false;

/*!
 * Counts the number of missed ADR acknowledgements
 */
static uint32_t AdrAckCounter = 0;

/*!
 * If the node has sent a FRAME_TYPE_DATA_CONFIRMED_UP this variable indicates
 * if the nodes needs to manage the server acknowledgement.
 */
static bool NodeAckRequested = false;

/*!
 * If the server has sent a FRAME_TYPE_DATA_CONFIRMED_DOWN this variable indicates
 * if the ACK bit must be set for the next transmission
 */
static bool SrvAckRequested = false;

/*!
 * Indicates if the MAC layer wants to send MAC commands
 */
static bool MacCommandsInNextTx = false;

/*!
 * Contains the current MacCommandsBuffer index
 */
static uint8_t MacCommandsBufferIndex = 0;

/*!
 * Contains the current MacCommandsBuffer index for MAC commands to repeat
 */
static uint8_t MacCommandsBufferToRepeatIndex = 0;

/*!
 * Buffer containing the MAC layer commands
 */
static uint8_t MacCommandsBuffer[LORA_MAC_COMMAND_MAX_LENGTH];

/*!
 * Buffer containing the MAC layer commands which must be repeated
 */
static uint8_t MacCommandsBufferToRepeat[LORA_MAC_COMMAND_MAX_LENGTH];

/*!
 * LoRaMac parameters
 */
LoRaMacParams_t LoRaMacParams;

/*!
 * LoRaMac default parameters
 */
LoRaMacParams_t LoRaMacParamsDefaults;

/*!
 * Uplink messages repetitions counter
 */
static uint8_t ChannelsNbRepCounter = 0;

/*!
 * Maximum duty cycle
 * \remark Possibility to shutdown the device.
 */
static uint8_t MaxDCycle = 0;

/*!
 * Aggregated duty cycle management
 */
static uint16_t AggregatedDCycle;
static TimerTime_t AggregatedLastTxDoneTime;
static TimerTime_t AggregatedTimeOff;

/*!
 * Enables/Disables duty cycle management (Test only)
 */
static bool DutyCycleOn;

/*!
 * Current channel index
 */
static uint8_t Channel;

/*!
 * Current channel index
 */
static uint8_t LastTxChannel;

/*!
 * Set to true, if the last uplink was a join request
 */
static bool LastTxIsJoinRequest;

/*!
 * Stores the time at LoRaMac initialization.
 *
 * \remark Used for the BACKOFF_DC computation.
 */
static TimerTime_t LoRaMacInitializationTime = 0;

static TimerSysTime_t LastTxSysTime = {0};

/*!
 * LoRaMac internal states
 */
enum eLoRaMacState {
    LORAMAC_IDLE = 0x00000000,
    LORAMAC_TX_RUNNING = 0x00000001,
    LORAMAC_RX = 0x00000002,
    LORAMAC_ACK_REQ = 0x00000004,
    LORAMAC_ACK_RETRY = 0x00000008,
    LORAMAC_TX_DELAYED = 0x00000010,
    LORAMAC_TX_CONFIG = 0x00000020,
    LORAMAC_RX_ABORT = 0x00000040,
};

/*!
 * LoRaMac internal state
 */
uint32_t LoRaMacState = LORAMAC_IDLE;

/*!
 * LoRaMac timer used to check the LoRaMacState (runs every second)
 */
static TimerEvent_t MacStateCheckTimer;

/*!
 * LoRaMac upper layer event functions
 */
static LoRaMacPrimitives_t* LoRaMacPrimitives;

/*!
 * LoRaMac upper layer callback functions
 */
static LoRaMacCallback_t* LoRaMacCallbacks;

/*!
 * Radio events function pointer
 */
static RadioEvents_t RadioEvents;

/*!
 * LoRaMac duty cycle delayed Tx timer
 */
static TimerEvent_t TxDelayedTimer;

#ifdef CONFIG_LORA_CAD
static TimerEvent_t TxImmediateTimer;
static uint8_t g_lora_cad_cnt = 1;
#endif
/*!
 * LoRaMac reception windows timers
 */
static TimerEvent_t RxWindowTimer1;
static TimerEvent_t RxWindowTimer2;

/*!
 * LoRaMac reception windows delay
 * \remark normal frame: RxWindowXDelay = ReceiveDelayX - RADIO_WAKEUP_TIME
 *         join frame  : RxWindowXDelay = JoinAcceptDelayX - RADIO_WAKEUP_TIME
 */
static uint32_t RxWindow1Delay;
static uint32_t RxWindow2Delay;

/*!
 * LoRaMac Rx windows configuration
 */
static RxConfigParams_t RxWindow1Config;
static RxConfigParams_t RxWindow2Config;

/*!
 * Acknowledge timeout timer. Used for packet retransmissions.
 */
static TimerEvent_t AckTimeoutTimer;

/*!
 * Number of trials to get a frame acknowledged
 */
static uint8_t AckTimeoutRetries = 1;

/*!
 * Number of trials to get a frame acknowledged
 */
static uint8_t AckTimeoutRetriesCounter = 1;

/*!
 * Indicates if the AckTimeout timer has expired or not
 */
static bool AckTimeoutRetry = false;

/*!
 * Last transmission time on air
 */
TimerTime_t TxTimeOnAir = 0;

/*!
 * Number of trials for the Join Request
 */
static uint8_t JoinRequestTrials;

/*!
 * Maximum number of trials for the Join Request
 */
static uint8_t MaxJoinRequestTrials;

/*!
 * Structure to hold an MCPS indication data.
 */
static McpsIndication_t McpsIndication;

/*!
 * Structure to hold MCPS confirm data.
 */
static McpsConfirm_t McpsConfirm;

/*!
 * Structure to hold MLME indication data.
 */
static MlmeIndication_t MlmeIndication;

/*!
 * Structure to hold MLME confirm data.
 */
static MlmeConfirm_t MlmeConfirm;

/*!
 * Holds the current rx window slot
 */
static LoRaMacRxSlot_t RxSlot;

/*!
 * LoRaMac tx/rx operation state
 */
LoRaMacFlags_t LoRaMacFlags;

#ifdef CONFIG_LWAN
static bool DownLinkFramePending = false;
#endif

/*!
 * \brief Function to be executed on Radio Tx Done event
 */
static void OnRadioTxDone(void);

/*!
 * \brief This function prepares the MAC to abort the execution of function
 *        OnRadioRxDone in case of a reception error.
 */
static void PrepareRxDoneAbort(void);

/*!
 * \brief Function to be executed on Radio Rx Done event
 */
static void OnRadioRxDone(uint8_t* payload, uint16_t size, int16_t rssi, int8_t snr);

/*!
 * \brief Function executed on Radio Tx Timeout event
 */
static void OnRadioTxTimeout(void);

/*!
 * \brief Function executed on Radio Rx error event
 */
static void OnRadioRxError(void);

/*!
 * \brief Function executed on Radio Rx Timeout event
 */
static void OnRadioRxTimeout(void);

#ifdef CONFIG_LORA_CAD
static void OnRadioCadDone(bool channelActivityDetected);
static void OnTxImmediateTimerEvent(void);
#endif

/*!
 * \brief Function executed on Resend Frame timer event.
 */
static void OnMacStateCheckTimerEvent(void);

/*!
 * \brief Function executed on duty cycle delayed Tx  timer event
 */
static void OnTxDelayedTimerEvent(void);

/*!
 * \brief Function executed on first Rx window timer event
 */
static void OnRxWindow1TimerEvent(void);

/*!
 * \brief Function executed on second Rx window timer event
 */
static void OnRxWindow2TimerEvent(void);

/*!
 * \brief Check if the OnAckTimeoutTimer has do be disabled. If so, the
 *        function disables it.
 *
 * \param [IN] nodeAckRequested Set to true, if the node has requested an ACK
 * \param [IN] class The device class
 * \param [IN] ackReceived Set to true, if the node has received an ACK
 * \param [IN] ackTimeoutRetriesCounter Retries counter for confirmed uplinks
 * \param [IN] ackTimeoutRetries Maximum retries for confirmed uplinks
 */
static void CheckToDisableAckTimeout(bool nodeAckRequested,
                                     DeviceClass_t devClass,
                                     bool ackReceived,
                                     uint8_t ackTimeoutRetriesCounter,
                                     uint8_t ackTimeoutRetries);

/*!
 * \brief Function executed on AckTimeout timer event
 */
static void OnAckTimeoutTimerEvent(void);

/*!
 * \brief Initializes and opens the reception window
 *
 * \param [IN] rxContinuous Set to true, if the RX is in continuous mode
 * \param [IN] maxRxWindow Maximum RX window timeout
 */
static void RxWindowSetup(bool rxContinuous, uint32_t maxRxWindow);

/*!
 * \brief Verifies if sticky MAC commands are pending.
 *
 * \retval [true: sticky MAC commands pending, false: No MAC commands pending]
 */
static bool IsStickyMacCommandPending(void);

/*!
 * \brief Configures the events to trigger an MLME-Indication with
 *        a MLME type of MLME_SCHEDULE_UPLINK.
 */
static void SetMlmeScheduleUplinkIndication(void);

/*!
 * \brief Switches the device class
 *
 * \param [IN] deviceClass Device class to switch to
 */
static LoRaMacStatus_t SwitchClass(DeviceClass_t deviceClass);

/*!
 * \brief Adds a new MAC command to be sent.
 *
 * \Remark MAC layer internal function
 *
 * \param [in] cmd MAC command to be added
 *                 [MOTE_MAC_LINK_CHECK_REQ,
 *                  MOTE_MAC_LINK_ADR_ANS,
 *                  MOTE_MAC_DUTY_CYCLE_ANS,
 *                  MOTE_MAC_RX2_PARAM_SET_ANS,
 *                  MOTE_MAC_DEV_STATUS_ANS
 *                  MOTE_MAC_NEW_CHANNEL_ANS]
 * \param [in] p1  1st parameter ( optional depends on the command )
 * \param [in] p2  2nd parameter ( optional depends on the command )
 *
 * \retval status  Function status [0: OK, 1: Unknown command, 2: Buffer full]
 */
static LoRaMacStatus_t AddMacCommand(uint8_t cmd, uint8_t p1, uint8_t p2);

/*!
 * \brief Parses the MAC commands which must be repeated.
 *
 * \Remark MAC layer internal function
 *
 * \param [IN] cmdBufIn  Buffer which stores the MAC commands to send
 * \param [IN] length  Length of the input buffer to parse
 * \param [OUT] cmdBufOut  Buffer which stores the MAC commands which must be
 *                         repeated.
 *
 * \retval Size of the MAC commands to repeat.
 */
static uint8_t ParseMacCommandsToRepeat(uint8_t* cmdBufIn, uint8_t length, uint8_t* cmdBufOut);

/*!
 * \brief Validates if the payload fits into the frame, taking the datarate
 *        into account.
 *
 * \details Refer to chapter 4.3.2 of the LoRaWAN specification, v1.0
 *
 * \param lenN Length of the application payload. The length depends on the
 *             datarate and is region specific
 *
 * \param datarate Current datarate
 *
 * \param fOptsLen Length of the fOpts field
 *
 * \retval [false: payload does not fit into the frame, true: payload fits into
 *          the frame]
 */
static bool ValidatePayloadLength(uint8_t lenN, int8_t datarate, uint8_t fOptsLen);

/*!
 * \brief Decodes MAC commands in the fOpts field and in the payload
 *
 * \param [IN] payload      A pointer to the payload
 * \param [IN] macIndex     The index of the payload where the MAC commands start
 * \param [IN] commandsSize The size of the MAC commands
 * \param [IN] snr          The SNR value  of the frame
 * \param [IN] rxSlot       The RX slot where the frame was received
 */
static void
ProcessMacCommands(uint8_t* payload, uint8_t macIndex, uint8_t commandsSize, uint8_t snr, LoRaMacRxSlot_t rxSlot);

/*!
 * \brief LoRaMAC layer generic send frame
 *
 * \param [IN] macHdr      MAC header field
 * \param [IN] fPort       MAC payload port
 * \param [IN] fBuffer     MAC data buffer to be sent
 * \param [IN] fBufferSize MAC data buffer size
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t Send(LoRaMacHeader_t* macHdr, uint8_t fPort, void* fBuffer, uint16_t fBufferSize);

/*!
 * \brief LoRaMAC layer frame buffer initialization
 *
 * \param [IN] macHdr      MAC header field
 * \param [IN] fCtrl       MAC frame control field
 * \param [IN] fOpts       MAC commands buffer
 * \param [IN] fPort       MAC payload port
 * \param [IN] fBuffer     MAC data buffer to be sent
 * \param [IN] fBufferSize MAC data buffer size
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t
PrepareFrame(LoRaMacHeader_t* macHdr, LoRaMacFrameCtrl_t* fCtrl, uint8_t fPort, void* fBuffer, uint16_t fBufferSize);

/*
 * \brief Schedules the frame according to the duty cycle
 *
 * \retval Status of the operation
 */
static LoRaMacStatus_t ScheduleTx(void);

/*
 * \brief Calculates the back-off time for the band of a channel.
 *
 * \param [IN] channel     The last Tx channel index
 */
static void CalculateBackOff(uint8_t channel);

/*!
 * \brief LoRaMAC layer prepared frame buffer transmission with channel specification
 *
 * \remark PrepareFrame must be called at least once before calling this
 *         function.
 *
 * \param [IN] channel     Channel to transmit on
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t SendFrameOnChannel(uint8_t channel);

/*!
 * \brief Sets the radio in continuous transmission mode
 *
 * \remark Uses the radio parameters set on the previous transmission.
 *
 * \param [IN] timeout     Time in seconds while the radio is kept in continuous wave mode
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t SetTxContinuousWave(uint16_t timeout);

/*!
 * \brief Sets the radio in continuous transmission mode
 *
 * \remark Uses the radio parameters set on the previous transmission.
 *
 * \param [IN] timeout     Time in seconds while the radio is kept in continuous wave mode
 * \param [IN] frequency   RF frequency to be set.
 * \param [IN] power       RF output power to be set.
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t SetTxContinuousWave1(uint16_t timeout, uint32_t frequency, uint8_t power);

/*!
 * \brief Sets the network to public or private. Updates the sync byte.
 *
 * \param [IN] enable if true, it enables a public network
 */
// static void SetPublicNetwork( bool enable );

/*!
 * \brief Resets MAC specific parameters to default
 */
static void ResetMacParameters(void);

/*!
 * \brief Resets MAC specific parameters to default
 *
 * \param [IN] fPort     The fPort
 *
 * \retval [false: fPort not allowed, true: fPort allowed]
 */
static bool IsFPortAllowed(uint8_t fPort);

/*!
 * \brief Opens up a continuous RX 2 window. This is used for
 *        class c devices.
 */
static void OpenContinuousRx2Window(void);

static void OnRadioTxDone(void)
{
    GetPhyParams_t getPhy;
    PhyParam_t phyParam;
    SetBandTxDoneParams_t txDone;
    TimerTime_t curTime = TimerGetCurrentTime();
    LastTxSysTime = TimerGetSysTime();

    if (LoRaMacDeviceClass != CLASS_C) {
        Radio.Sleep();
    } else {
        OpenContinuousRx2Window();
    }

    // Setup timers
    if (IsRxWindowsEnabled == true) {
        TimerSetValue(&RxWindowTimer1, RxWindow1Delay);
        TimerStart(&RxWindowTimer1);
        if (LoRaMacDeviceClass != CLASS_C) {
            TimerSetValue(&RxWindowTimer2, RxWindow2Delay);
            TimerStart(&RxWindowTimer2);
        }
        if ((LoRaMacDeviceClass == CLASS_C) || (NodeAckRequested == true)) {
            getPhy.Attribute = PHY_ACK_TIMEOUT;
            phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
            TimerSetValue(&AckTimeoutTimer, RxWindow2Delay + phyParam.Value);
            TimerStart(&AckTimeoutTimer);
        }
    } else {
        McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK;
        LoRaMacConfirmQueueSetStatusCmn(LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT);

        if (LoRaMacFlags.Value == 0) {
            LoRaMacFlags.Bits.McpsReq = 1;
        }
        LoRaMacFlags.Bits.MacDone = 1;
    }

    // Verify if the last uplink was a join request
    if ((LoRaMacFlags.Bits.MlmeReq == 1) && (LoRaMacConfirmQueueIsCmdActive(MLME_JOIN) == true)) {
        LastTxIsJoinRequest = true;
    } else {
        LastTxIsJoinRequest = false;
    }

    // Store last Tx channel
    LastTxChannel = Channel;
    // Update last tx done time for the current channel
    txDone.Channel = Channel;
    txDone.Joined = IsLoRaMacNetworkJoined;
    txDone.LastTxDoneTime = curTime;
    RegionSetBandTxDone(LoRaMacRegion, &txDone);
    // Update Aggregated last tx done time
    AggregatedLastTxDoneTime = curTime;

    if (NodeAckRequested == false) {
        McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK;
#ifdef CONFIG_LWAN
        McpsConfirm.NbRetries++;
#endif
        ChannelsNbRepCounter++;
    }

#ifdef CONFIG_LWAN
    lwan_dev_status_set(DEVICE_STATUS_SEND_PASS);
#endif
}

static void PrepareRxDoneAbort(void)
{
    LoRaMacState |= LORAMAC_RX_ABORT;

    if (NodeAckRequested) {
        OnAckTimeoutTimerEvent();
    }

    TimerStop(&RxWindowTimer1);
    TimerStop(&RxWindowTimer2);

    LoRaMacFlags.Bits.McpsInd = 1;
    LoRaMacFlags.Bits.MacDone = 1;

    // Trig OnMacCheckTimerEvent call as soon as possible
    TimerSetValue(&MacStateCheckTimer, 1);
    TimerStart(&MacStateCheckTimer);
}

static void OnRadioRxDone(uint8_t* payload, uint16_t size, int16_t rssi, int8_t snr)
{
    LoRaMacHeader_t macHdr;
    LoRaMacFrameCtrl_t fCtrl;
    ApplyCFListParams_t applyCFList;
    GetPhyParams_t getPhy;
    PhyParam_t phyParam;

    uint8_t pktHeaderLen = 0;
    uint32_t address = 0;
    uint8_t appPayloadStartIndex = 0;
    uint8_t port = 0xFF;
    uint8_t frameLen = 0;
    uint32_t mic = 0;
    uint32_t micRx = 0;

    uint16_t sequenceCounter = 0;
    uint16_t sequenceCounterPrev = 0;
    uint16_t sequenceCounterDiff = 0;
    uint32_t downLinkCounter = 0;

    MulticastParams_t* curMulticastParams = NULL;
    uint8_t* nwkSKey = LoRaMacNwkSKey;
    uint8_t* appSKey = LoRaMacAppSKey;

    uint8_t multicast = 0;

    bool isMicOk = false;

    McpsConfirm.AckReceived = false;
#ifdef CONFIG_LWAN
    MlmeConfirm.Rssi = rssi;
    MlmeConfirm.Snr = snr;
    McpsIndication.DevTimeAnsReceived = false;
    McpsIndication.LinkCheckAnsReceived = false;
    McpsIndication.UplinkNeeded = false;
#endif
    McpsIndication.Rssi = rssi;
    McpsIndication.Snr = snr;
    McpsIndication.RxSlot = RxSlot;
    McpsIndication.Port = 0;
    McpsIndication.Multicast = 0;
    McpsIndication.FramePending = 0;
    McpsIndication.Buffer = NULL;
    McpsIndication.BufferSize = 0;
    McpsIndication.RxData = false;
    McpsIndication.AckReceived = false;
    McpsIndication.DownLinkCounter = 0;
    McpsIndication.McpsIndication = MCPS_UNCONFIRMED;

    Radio.Sleep();
    TimerStop(&RxWindowTimer2);

    // This function must be called even if we are not in class b mode yet.
    if (LoRaMacClassBRxBeacon(payload, size) == true) {
        MlmeIndication.BeaconInfo.Rssi = rssi;
        MlmeIndication.BeaconInfo.Snr = snr;

        LOG_PRINTF(LL_VDEBUG, "receive beacon\r\n");
        return;
    }
    // Check if we expect a ping or a multicast slot.
    if (LoRaMacDeviceClass == CLASS_B) {
        if (LoRaMacClassBIsPingExpected() == true) {
            LoRaMacClassBSetPingSlotState(PINGSLOT_STATE_SET_TIMER);
            LoRaMacClassBPingSlotTimerEvent();
            McpsIndication.RxSlot = RX_SLOT_WIN_PING_SLOT;
        } else if (LoRaMacClassBIsMulticastExpected() == true) {
            LoRaMacClassBSetMulticastSlotState(PINGSLOT_STATE_SET_TIMER);
            LoRaMacClassBMulticastSlotTimerEvent();
            McpsIndication.RxSlot = RX_SLOT_WIN_MULTICAST_SLOT;
        }
    }

    macHdr.Value = payload[pktHeaderLen++];

    switch (macHdr.Bits.MType) {
        case FRAME_TYPE_JOIN_ACCEPT:
            if (IsLoRaMacNetworkJoined == true) {
                McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
                PrepareRxDoneAbort();
                return;
            }
            LoRaMacJoinDecrypt(payload + 1, size - 1, LoRaMacAppKey, LoRaMacRxPayload + 1);

            LoRaMacRxPayload[0] = macHdr.Value;
            LoRaMacJoinComputeMic(LoRaMacRxPayload, size - LORAMAC_MFR_LEN, LoRaMacAppKey, &mic);

            micRx |= (uint32_t)LoRaMacRxPayload[size - LORAMAC_MFR_LEN];
            micRx |= ((uint32_t)LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 1] << 8);
            micRx |= ((uint32_t)LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 2] << 16);
            micRx |= ((uint32_t)LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 3] << 24);

            if (LoRaMacConfirmQueueIsCmdActive(MLME_JOIN) == true) {
                if (micRx == mic) {
                    LoRaMacJoinComputeSKeys(LoRaMacAppKey, LoRaMacRxPayload + 1, LoRaMacDevNonce, LoRaMacNwkSKey,
                                            LoRaMacAppSKey);

                    LoRaMacNetID = (uint32_t)LoRaMacRxPayload[4];
                    LoRaMacNetID |= ((uint32_t)LoRaMacRxPayload[5] << 8);
                    LoRaMacNetID |= ((uint32_t)LoRaMacRxPayload[6] << 16);

                    LoRaMacDevAddr = (uint32_t)LoRaMacRxPayload[7];
                    LoRaMacDevAddr |= ((uint32_t)LoRaMacRxPayload[8] << 8);
                    LoRaMacDevAddr |= ((uint32_t)LoRaMacRxPayload[9] << 16);
                    LoRaMacDevAddr |= ((uint32_t)LoRaMacRxPayload[10] << 24);

                    // DLSettings
                    LoRaMacParams.Rx1DrOffset = (LoRaMacRxPayload[11] >> 4) & 0x07;
                    LoRaMacParams.Rx2Channel.Datarate = LoRaMacRxPayload[11] & 0x0F;

                    // RxDelay
                    LoRaMacParams.ReceiveDelay1 = (LoRaMacRxPayload[12] & 0x0F);
                    if (LoRaMacParams.ReceiveDelay1 == 0) {
                        LoRaMacParams.ReceiveDelay1 = 1;
                    }
                    LoRaMacParams.ReceiveDelay1 *= 1000;
                    LoRaMacParams.ReceiveDelay2 = LoRaMacParams.ReceiveDelay1 + 1000;

                    LOG_PRINTF(LL_VDEBUG, "Rx1DrOffset:%u Rx2Channel.Datarate:%u ReceiveDelay1:%u\r\n",
                               LoRaMacParams.Rx1DrOffset, LoRaMacParams.Rx2Channel.Datarate,
                               (unsigned int)LoRaMacParams.ReceiveDelay1);

                    // Apply CF list
                    applyCFList.Payload = &LoRaMacRxPayload[13];
                    // Size of the regular payload is 12. Plus 1 byte MHDR and 4 bytes MIC
                    applyCFList.Size = size - 17;

                    RegionApplyCFList(LoRaMacRegion, &applyCFList);

                    LoRaMacConfirmQueueSetStatus(LORAMAC_EVENT_INFO_STATUS_OK, MLME_JOIN);
                    IsLoRaMacNetworkJoined = true;
                    // Joined save its DR using LoRaMacParams.ChannelsDatarate, if set it will be default
                    // LoRaMacParams.ChannelsDatarate = LoRaMacParamsDefaults.ChannelsDatarate;
                } else {
                    LoRaMacConfirmQueueSetStatus(LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL, MLME_JOIN);
                }
            }
            break;
        case FRAME_TYPE_DATA_CONFIRMED_DOWN:
        case FRAME_TYPE_DATA_UNCONFIRMED_DOWN: {
            // Check if the received payload size is valid
            getPhy.UplinkDwellTime = LoRaMacParams.DownlinkDwellTime;
            getPhy.Datarate = McpsIndication.RxDatarate;
            getPhy.Attribute = PHY_MAX_PAYLOAD;

            // Get the maximum payload length
            if (LoRaMacParams.RepeaterSupport == true) {
                getPhy.Attribute = PHY_MAX_PAYLOAD_REPEATER;
            }
            phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
            if (MAX(0, (int16_t)((int16_t)size - (int16_t)LORA_MAC_FRMPAYLOAD_OVERHEAD)) > phyParam.Value) {
                McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
                PrepareRxDoneAbort();
                return;
            }

            address = payload[pktHeaderLen++];
            address |= ((uint32_t)payload[pktHeaderLen++] << 8);
            address |= ((uint32_t)payload[pktHeaderLen++] << 16);
            address |= ((uint32_t)payload[pktHeaderLen++] << 24);

            fCtrl.Value = payload[pktHeaderLen++];

            if (address != LoRaMacDevAddr) {
                curMulticastParams = MulticastChannels;
                while (curMulticastParams != NULL) {
                    if (address == curMulticastParams->Address) {
                        multicast = 1;
                        nwkSKey = curMulticastParams->NwkSKey;
                        appSKey = curMulticastParams->AppSKey;
                        downLinkCounter = curMulticastParams->DownLinkCounter;
                        break;
                    }
                    curMulticastParams = curMulticastParams->Next;
                }
                if (multicast == 0) {
                    // We are not the destination of this frame.
                    McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ADDRESS_FAIL;
                    PrepareRxDoneAbort();
                    return;
                }
                if ((macHdr.Bits.MType != FRAME_TYPE_DATA_UNCONFIRMED_DOWN) || (fCtrl.Bits.Ack == 1) ||
                    (fCtrl.Bits.AdrAckReq == 1)) {
                    // Wrong multicast message format. Refer to chapter 11.2.2 of the specification
                    McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_MULTICAST_FAIL;
                    PrepareRxDoneAbort();
                    return;
                }
            } else {
                multicast = 0;
                nwkSKey = LoRaMacNwkSKey;
                appSKey = LoRaMacAppSKey;
                downLinkCounter = DownLinkCounter;
            }

            sequenceCounter = (uint16_t)payload[pktHeaderLen++];
            sequenceCounter |= (uint16_t)payload[pktHeaderLen++] << 8;

            appPayloadStartIndex = 8 + fCtrl.Bits.FOptsLen;

            micRx |= (uint32_t)payload[size - LORAMAC_MFR_LEN];
            micRx |= ((uint32_t)payload[size - LORAMAC_MFR_LEN + 1] << 8);
            micRx |= ((uint32_t)payload[size - LORAMAC_MFR_LEN + 2] << 16);
            micRx |= ((uint32_t)payload[size - LORAMAC_MFR_LEN + 3] << 24);

            sequenceCounterPrev = (uint16_t)downLinkCounter;
            sequenceCounterDiff = (sequenceCounter - sequenceCounterPrev);

            if (sequenceCounterDiff < (1 << 15)) {
                downLinkCounter += sequenceCounterDiff;
                LoRaMacComputeMic(payload, size - LORAMAC_MFR_LEN, nwkSKey, address, DOWN_LINK, downLinkCounter, &mic);
                if (micRx == mic) {
                    isMicOk = true;
                }
            } else {
                // check for sequence roll-over
                uint32_t downLinkCounterTmp = downLinkCounter + 0x10000 + (int16_t)sequenceCounterDiff;
                LoRaMacComputeMic(payload, size - LORAMAC_MFR_LEN, nwkSKey, address, DOWN_LINK, downLinkCounterTmp,
                                  &mic);
                if (micRx == mic) {
                    isMicOk = true;
                    downLinkCounter = downLinkCounterTmp;
                }
            }

            // Check for a the maximum allowed counter difference
            getPhy.Attribute = PHY_MAX_FCNT_GAP;
            phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
            if (sequenceCounterDiff >= phyParam.Value) {
                McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_TOO_MANY_FRAMES_LOSS;
                McpsIndication.DownLinkCounter = downLinkCounter;
                PrepareRxDoneAbort();
                return;
            }

            if (isMicOk == true) {
                McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_OK;
                McpsIndication.Multicast = multicast;
                McpsIndication.FramePending = fCtrl.Bits.FPending;
                McpsIndication.Buffer = NULL;
                McpsIndication.BufferSize = 0;
                McpsIndication.DownLinkCounter = downLinkCounter;
#ifdef CONFIG_LWAN
                if ((LoRaMacDeviceClass == CLASS_A &&
                     (McpsIndication.FramePending || (macHdr.Bits.MType == FRAME_TYPE_DATA_CONFIRMED_DOWN))) ||
                    (LoRaMacDeviceClass != CLASS_A && DownLinkFramePending && !McpsIndication.FramePending)) {
                    McpsIndication.UplinkNeeded = true;
                }
                DownLinkFramePending = fCtrl.Bits.FPending;
#endif
                McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK;

                AdrAckCounter = 0;
                MacCommandsBufferToRepeatIndex = 0;

                // Update 32 bits downlink counter
                if (multicast == 1) {
                    McpsIndication.McpsIndication = MCPS_MULTICAST;

                    if ((curMulticastParams->DownLinkCounter == downLinkCounter) &&
                        (curMulticastParams->DownLinkCounter != 0)) {
                        McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED;
                        McpsIndication.DownLinkCounter = downLinkCounter;
                        PrepareRxDoneAbort();
                        return;
                    }
                    curMulticastParams->DownLinkCounter = downLinkCounter;
                } else {
                    if (macHdr.Bits.MType == FRAME_TYPE_DATA_CONFIRMED_DOWN) {
#ifdef CONFIG_LWAN
                        if (LoRaMacDeviceClass != CLASS_A && McpsIndication.FramePending) {
                            SrvAckRequested = false;
                        } else {
                            SrvAckRequested = true;
                        }
#else
                        SrvAckRequested = true;
#endif
                        McpsIndication.McpsIndication = MCPS_CONFIRMED;

                        if ((DownLinkCounter == downLinkCounter) && (DownLinkCounter != 0)) {
                            // Duplicated confirmed downlink. Skip indication.
                            // In this case, the MAC layer shall accept the MAC commands
                            // which are included in the downlink retransmission.
                            // It should not provide the same frame to the application
                            // layer again.
                            LoRaMacFlags.Bits.McpsIndSkip = 1;
                        }
                    } else {
                        SrvAckRequested = false;
                        McpsIndication.McpsIndication = MCPS_UNCONFIRMED;

                        if ((DownLinkCounter == downLinkCounter) && (DownLinkCounter != 0)) {
                            McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED;
                            McpsIndication.DownLinkCounter = downLinkCounter;
                            PrepareRxDoneAbort();
                            return;
                        }
                    }
                    DownLinkCounter = downLinkCounter;
                }

                // This must be done before parsing the payload and the MAC commands.
                // We need to reset the MacCommandsBufferIndex here, since we need
                // to take retransmissions and repetitions into account. Error cases
                // will be handled in function OnMacStateCheckTimerEvent.
                if (McpsConfirm.McpsRequest == MCPS_CONFIRMED) {
                    if (fCtrl.Bits.Ack == 1) {
                        // Reset MacCommandsBufferIndex when we have received an ACK.
                        MacCommandsBufferIndex = 0;
                        // Update acknowledgement information
                        McpsConfirm.AckReceived = fCtrl.Bits.Ack;
                        McpsIndication.AckReceived = fCtrl.Bits.Ack;
                    }
                } else {
                    // Reset the variable if we have received any valid frame.
                    MacCommandsBufferIndex = 0;
                }

                // Process payload and MAC commands
                if (((size - 4) - appPayloadStartIndex) > 0) {
                    port = payload[appPayloadStartIndex++];
                    frameLen = (size - 4) - appPayloadStartIndex;

#if defined(CONFIG_LINKWAN) && defined(CONFIG_LORA_VERIFY)
                    // Add for linkwan verify test
                    if (!frameLen) {
                        LoRaMacFlags.Bits.McpsIndSkip = 1;
                        // This is not a valid frame. Drop it and reset the ACK bits
                        McpsConfirm.AckReceived = false;
                        McpsIndication.AckReceived = false;
                        SrvAckRequested = false;

                        SetMlmeScheduleUplinkIndication();

                        LOG_PRINTF(LL_DEBUG, "Invalid frame with only frame port\r\n");
                    }
#endif

                    McpsIndication.Port = port;

                    if (port == 0) {
                        // Only allow frames which do not have fOpts
                        if ((fCtrl.Bits.FOptsLen == 0) && (multicast == 0)) {
                            LoRaMacPayloadDecrypt(payload + appPayloadStartIndex, frameLen, nwkSKey, address, DOWN_LINK,
                                                  downLinkCounter, LoRaMacRxPayload);
#ifdef CONFIG_LWAN
                            if (fCtrl.Bits.Adr != AdrCtrlOn) {
                                uint8_t adr;

                                adr = fCtrl.Bits.Adr;
                                lwan_mac_config_set(MAC_CONFIG_ADR_ENABLE, (void*)&adr);
                                AdrCtrlOn = fCtrl.Bits.Adr;
                            }
#endif

                            // Decode frame payload MAC commands
                            ProcessMacCommands(LoRaMacRxPayload, 0, frameLen, snr, McpsIndication.RxSlot);
                        } else {
                            LoRaMacFlags.Bits.McpsIndSkip = 1;
                            // This is not a valid frame. Drop it and reset the ACK bits
                            McpsConfirm.AckReceived = false;
                            McpsIndication.AckReceived = false;
                            LOG_PRINTF(LL_VDEBUG, "CMD exist at FRMpayload and Fopts, ignore it\r\n");
                        }
                    } else {
#ifdef CONFIG_LWAN
                        if (fCtrl.Bits.Adr != AdrCtrlOn) {
                            uint8_t adr;

                            adr = fCtrl.Bits.Adr;
                            lwan_mac_config_set(MAC_CONFIG_ADR_ENABLE, (void*)&adr);
                            AdrCtrlOn = fCtrl.Bits.Adr;
                        }
#endif

                        if ((fCtrl.Bits.FOptsLen > 0) && (multicast == 0)) {
                            // Decode Options field MAC commands. Omit the fPort.
                            ProcessMacCommands(payload, 8, appPayloadStartIndex - 1, snr, McpsIndication.RxSlot);
                        }

                        LoRaMacPayloadDecrypt(payload + appPayloadStartIndex, frameLen, appSKey, address, DOWN_LINK,
                                              downLinkCounter, LoRaMacRxPayload);

                        McpsIndication.Buffer = LoRaMacRxPayload;
                        McpsIndication.BufferSize = frameLen;
                        McpsIndication.RxData = true;
                    }
                } else {
#ifdef CONFIG_LWAN
                    if (fCtrl.Bits.Adr != AdrCtrlOn) {
                        uint8_t adr;

                        adr = fCtrl.Bits.Adr;
                        lwan_mac_config_set(MAC_CONFIG_ADR_ENABLE, (void*)&adr);
                        AdrCtrlOn = fCtrl.Bits.Adr;
                    }
#endif

                    if (fCtrl.Bits.FOptsLen > 0) {
                        // Decode Options field MAC commands
                        ProcessMacCommands(payload, 8, appPayloadStartIndex, snr, McpsIndication.RxSlot);
                    }
                }

                // Provide always an indication, skip the callback to the user application,
                // in case of a confirmed downlink retransmission.
                LoRaMacFlags.Bits.McpsInd = 1;
            } else {
                LOG_PRINTF(LL_VDEBUG, "MIC verify failed ignore the frame\r\n");
                McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_MIC_FAIL;

                PrepareRxDoneAbort();
                return;
            }
        } break;
        case FRAME_TYPE_PROPRIETARY: {
            memcpy1(LoRaMacRxPayload, &payload[pktHeaderLen], size);

            McpsIndication.McpsIndication = MCPS_PROPRIETARY;
            McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_OK;
            McpsIndication.Buffer = LoRaMacRxPayload;
            McpsIndication.BufferSize = size - pktHeaderLen;

            LoRaMacFlags.Bits.McpsInd = 1;
            break;
        }
        default:
            LOG_PRINTF(LL_VDEBUG, "Download frame %d being received but not process it\r\n", macHdr.Bits.MType);

            McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
            PrepareRxDoneAbort();
            break;
    }
    // Verify if we need to disable the AckTimeoutTimer
    CheckToDisableAckTimeout(NodeAckRequested, LoRaMacDeviceClass, McpsConfirm.AckReceived, AckTimeoutRetriesCounter,
                             AckTimeoutRetries);
    if (AckTimeoutTimer.IsRunning == false) {  // Procedure is completed when the AckTimeoutTimer is not running anymore
        LoRaMacFlags.Bits.MacDone = 1;

        // Trig OnMacCheckTimerEvent call as soon as possible
        TimerSetValue(&MacStateCheckTimer, 1);
        TimerStart(&MacStateCheckTimer);
    }
}

static void OnRadioTxTimeout(void)
{
    if (LoRaMacDeviceClass != CLASS_C) {
        Radio.Sleep();
    } else {
        OpenContinuousRx2Window();
    }

    McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT;
    LoRaMacConfirmQueueSetStatusCmn(LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT);
    LoRaMacFlags.Bits.MacDone = 1;
#ifdef CONFIG_LWAN
    lwan_dev_status_set(DEVICE_STATUS_SEND_FAIL);
#endif
}

static void OnRadioRxError(void)
{
    bool classBRx = false;

    if (LoRaMacDeviceClass != CLASS_C) {
        Radio.Sleep();
    }

    if (LoRaMacClassBIsBeaconExpected() == true) {
        LoRaMacClassBSetBeaconState(BEACON_STATE_TIMEOUT);
        LoRaMacClassBBeaconTimerEvent();
        classBRx = true;
    }
    if (LoRaMacDeviceClass == CLASS_B) {
        if (LoRaMacClassBIsPingExpected() == true) {
            LoRaMacClassBSetPingSlotState(PINGSLOT_STATE_SET_TIMER);
            LoRaMacClassBPingSlotTimerEvent();
            classBRx = true;
        }
        if (LoRaMacClassBIsMulticastExpected() == true) {
            LoRaMacClassBSetMulticastSlotState(PINGSLOT_STATE_SET_TIMER);
            LoRaMacClassBMulticastSlotTimerEvent();
            classBRx = true;
        }
    }

    if (classBRx == false) {
        if (RxSlot == RX_SLOT_WIN_1) {
            if (NodeAckRequested == true) {
                McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX1_ERROR;
            }
            LoRaMacConfirmQueueSetStatusCmn(LORAMAC_EVENT_INFO_STATUS_RX1_ERROR);

            if (LoRaMacDeviceClass != CLASS_C) {
                if (TimerGetElapsedTime(AggregatedLastTxDoneTime) >= RxWindow2Delay) {
                    TimerStop(&RxWindowTimer2);
                    LoRaMacFlags.Bits.MacDone = 1;
                }
            }
        } else {
            if (NodeAckRequested == true) {
                McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX2_ERROR;
            }
            LoRaMacConfirmQueueSetStatusCmn(LORAMAC_EVENT_INFO_STATUS_RX2_ERROR);

            if (LoRaMacDeviceClass != CLASS_C) {
                LoRaMacFlags.Bits.MacDone = 1;
            }
        }
    }

    if (LoRaMacDeviceClass == CLASS_C) {
        OpenContinuousRx2Window();
    }
}

static void OnRadioRxTimeout(void)
{
    bool classBRx = false;

    if (LoRaMacDeviceClass != CLASS_C) {
        Radio.Sleep();
    }

    if (LoRaMacClassBIsBeaconExpected() == true) {
        LoRaMacClassBSetBeaconState(BEACON_STATE_TIMEOUT);
        LoRaMacClassBBeaconTimerEvent();
        classBRx = true;
    }
    if (LoRaMacDeviceClass == CLASS_B) {
        if (LoRaMacClassBIsPingExpected() == true) {
            LoRaMacClassBSetPingSlotState(PINGSLOT_STATE_SET_TIMER);
            LoRaMacClassBPingSlotTimerEvent();
            classBRx = true;
        }
        if (LoRaMacClassBIsMulticastExpected() == true) {
            LoRaMacClassBSetMulticastSlotState(PINGSLOT_STATE_SET_TIMER);
            LoRaMacClassBMulticastSlotTimerEvent();
            classBRx = true;
        }
    }

    if (classBRx == false) {
        if (RxSlot == RX_SLOT_WIN_1) {
            if (NodeAckRequested == true) {
                McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX1_TIMEOUT;
            }
            LoRaMacConfirmQueueSetStatusCmn(LORAMAC_EVENT_INFO_STATUS_RX1_TIMEOUT);

            if (LoRaMacDeviceClass != CLASS_C) {
                if (TimerGetElapsedTime(AggregatedLastTxDoneTime) >= RxWindow2Delay) {
                    TimerStop(&RxWindowTimer2);
                    LoRaMacFlags.Bits.MacDone = 1;
                }
            }
        } else {
            if (NodeAckRequested == true) {
                McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT;
            }
            LoRaMacConfirmQueueSetStatusCmn(LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT);

            if (LoRaMacDeviceClass != CLASS_C) {
                LoRaMacFlags.Bits.MacDone = 1;
            }
        }
    }

    if (LoRaMacDeviceClass == CLASS_C) {
        OpenContinuousRx2Window();
    }
}

#ifdef CONFIG_LORA_CAD
static void OnRadioCadDone(bool channelActivityDetected)
{
    Radio.Sleep();

    if (channelActivityDetected && g_lora_cad_cnt < LORA_CAD_CNT_MAX) {
        // Send later - prepare timer
        g_lora_cad_cnt++;
        LoRaMacState |= LORAMAC_TX_DELAYED;
        TimerSetValue(&TxDelayedTimer, LORA_CAD_DELAY);
        TimerStart(&TxDelayedTimer);
    } else {
        // Try to send now
        g_lora_cad_cnt = 1;
        TimerSetValue(&TxImmediateTimer, 1);
        TimerStart(&TxImmediateTimer);
    }
}
#endif

static void OnMacStateCheckTimerEvent(void)
{
    GetPhyParams_t getPhy;
    PhyParam_t phyParam;
    bool noTx = false;

    TimerStop(&MacStateCheckTimer);

    if (LoRaMacFlags.Bits.MacDone == 1) {
        if ((LoRaMacState & LORAMAC_RX_ABORT) == LORAMAC_RX_ABORT) {
            LoRaMacState &= ~LORAMAC_RX_ABORT;
            LoRaMacState &= ~LORAMAC_TX_RUNNING;
        }

        if ((LoRaMacFlags.Bits.MlmeReq == 1) || ((LoRaMacFlags.Bits.McpsReq == 1))) {
            // Get a status of any request and check if we have a TX timeout
            MlmeConfirm.Status = LoRaMacConfirmQueueGetStatusCmn();
            if ((McpsConfirm.Status == LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT) ||
                (MlmeConfirm.Status == LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT)) {
                // Stop transmit cycle due to tx timeout.
                LoRaMacState &= ~LORAMAC_TX_RUNNING;
                MacCommandsBufferIndex = 0;
                McpsConfirm.NbRetries = AckTimeoutRetriesCounter;
                McpsConfirm.AckReceived = false;
                McpsConfirm.TxTimeOnAir = 0;
                noTx = true;
            }

            if ((LoRaMacConfirmQueueIsCmdActive(MLME_BEACON_ACQUISITION) == true) && (LoRaMacFlags.Bits.McpsReq == 0)) {
                if (LoRaMacFlags.Bits.MlmeReq == 1) {
                    noTx = true;
                    LoRaMacState &= ~LORAMAC_TX_RUNNING;
                }
            }
        }

        if ((NodeAckRequested == false) && (noTx == false)) {
            if ((LoRaMacFlags.Bits.MlmeReq == 1) || ((LoRaMacFlags.Bits.McpsReq == 1))) {
                if ((LoRaMacFlags.Bits.MlmeReq == 1) && (LoRaMacConfirmQueueIsCmdActive(MLME_JOIN) == true)) {
                    // Procedure for the join request
                    MlmeConfirm.NbRetries = JoinRequestTrials;

                    if (LoRaMacConfirmQueueGetStatus(MLME_JOIN) == LORAMAC_EVENT_INFO_STATUS_OK) {
                        // Node joined successfully
                        UpLinkCounter = 0;
                        LOG_PRINTF(LL_VDEBUG, "Join done, UpLinkCounter:%u\r\n", (unsigned int)UpLinkCounter);
                        ChannelsNbRepCounter = 0;
                        LoRaMacState &= ~LORAMAC_TX_RUNNING;
                    } else {
                        if (JoinRequestTrials >= MaxJoinRequestTrials) {
                            LoRaMacState &= ~LORAMAC_TX_RUNNING;
                        } else {
                            LoRaMacFlags.Bits.MacDone = 0;
                            // Sends the same frame again
                            OnTxDelayedTimerEvent();
                        }
                    }
                } else {
                    // Procedure for all other frames
                    if ((ChannelsNbRepCounter >= LoRaMacParams.ChannelsNbRep) || (LoRaMacFlags.Bits.McpsInd == 1)) {
                        if (LoRaMacFlags.Bits.McpsInd == 0) {
                            // Maximum repetitions without downlink. Reset MacCommandsBufferIndex. Increase ADR Ack
                            // counter. Only process the case when the MAC did not receive a downlink.
                            MacCommandsBufferIndex = 0;
                            AdrAckCounter++;
                        }

                        ChannelsNbRepCounter = 0;

                        if (IsUpLinkCounterFixed == false) {
                            UpLinkCounter++;
                            LOG_PRINTF(LL_VDEBUG, "Unconfirmed data, UpLinkCounter:%u\r\n",
                                       (unsigned int)UpLinkCounter);
                        }

                        LoRaMacState &= ~LORAMAC_TX_RUNNING;
                    } else {
                        LoRaMacFlags.Bits.MacDone = 0;
                        // Sends the same frame again
                        OnTxDelayedTimerEvent();
                    }
                }
            }
        }

        if (LoRaMacFlags.Bits.McpsInd == 1) {
            // Procedure if we received a frame
            if ((McpsConfirm.AckReceived == true) || (AckTimeoutRetriesCounter > AckTimeoutRetries)) {
                AckTimeoutRetry = false;
                NodeAckRequested = false;
                if (IsUpLinkCounterFixed == false) {
                    UpLinkCounter++;
                    LOG_PRINTF(LL_VDEBUG, "Confirmed data received ACK, UpLinkCounter:%u\r\n",
                               (unsigned int)UpLinkCounter);
                }
                McpsConfirm.NbRetries = AckTimeoutRetriesCounter;

                LoRaMacState &= ~LORAMAC_TX_RUNNING;
            }
#ifdef CONFIG_LWAN
        } else {
            if (!((LoRaMacFlags.Bits.MlmeReq == 1) && (MlmeConfirm.MlmeRequest == MLME_JOIN)))
                lwan_dev_status_set(DEVICE_STATUS_SEND_PASS_WITHOUT_DL);
        }
#else
        }
#endif

        if ((AckTimeoutRetry == true) && ((LoRaMacState & LORAMAC_TX_DELAYED) == 0)) {
            // Retransmissions procedure for confirmed uplinks
            AckTimeoutRetry = false;
            if ((AckTimeoutRetriesCounter < AckTimeoutRetries) && (AckTimeoutRetriesCounter <= MAX_ACK_RETRIES)) {
                AckTimeoutRetriesCounter++;

                if ((AckTimeoutRetriesCounter % 2) == 1) {
                    getPhy.Attribute = PHY_NEXT_LOWER_TX_DR;
                    getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;
                    getPhy.Datarate = LoRaMacParams.ChannelsDatarate;
                    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
                    LoRaMacParams.ChannelsDatarate = phyParam.Value;
                }
                // Try to send the frame again
                if (ScheduleTx() == LORAMAC_STATUS_OK) {
                    LoRaMacFlags.Bits.MacDone = 0;
                } else {
                    // The DR is not applicable for the payload size
                    McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_TX_DR_PAYLOAD_SIZE_ERROR;

                    MacCommandsBufferIndex = 0;
                    LoRaMacState &= ~LORAMAC_TX_RUNNING;
                    NodeAckRequested = false;
                    McpsConfirm.AckReceived = false;
                    McpsConfirm.NbRetries = AckTimeoutRetriesCounter;
                    McpsConfirm.Datarate = LoRaMacParams.ChannelsDatarate;
                    if (IsUpLinkCounterFixed == false) {
                        UpLinkCounter++;
                        LOG_PRINTF(LL_VDEBUG, "Confirmed data can't send after decrease DR, UpLinkCounter:%u\r\n",
                                   (unsigned int)UpLinkCounter);
                    }
                }
            } else {
                RegionInitDefaults(LoRaMacRegion, INIT_TYPE_RESTORE);

                LoRaMacState &= ~LORAMAC_TX_RUNNING;

                MacCommandsBufferIndex = 0;
                NodeAckRequested = false;
                McpsConfirm.AckReceived = false;
                McpsConfirm.NbRetries = AckTimeoutRetriesCounter;
                if (IsUpLinkCounterFixed == false) {
                    UpLinkCounter++;
                    LOG_PRINTF(LL_VDEBUG, "Confirmed data exceed retry times, UpLinkCounter:%u\r\n",
                               (unsigned int)UpLinkCounter);
                }
            }
        }
    }

    // Handle events
    if (LoRaMacState == LORAMAC_IDLE) {
        if (LoRaMacFlags.Bits.McpsReq == 1) {
            LoRaMacFlags.Bits.McpsReq = 0;
            LoRaMacPrimitives->MacMcpsConfirm(&McpsConfirm);
        }

        if (LoRaMacFlags.Bits.MlmeReq == 1) {
            LoRaMacFlags.Bits.MlmeReq = 0;
            LoRaMacConfirmQueueHandleCb(&MlmeConfirm);
            if (LoRaMacConfirmQueueGetCnt() > 0) {
                LoRaMacFlags.Bits.MlmeReq = 1;
            }
        }

        // Handle MLME indication
        if (LoRaMacFlags.Bits.MlmeInd == 1) {
            LoRaMacPrimitives->MacMlmeIndication(&MlmeIndication);
            LoRaMacFlags.Bits.MlmeInd = 0;

#ifdef CONFIG_LWAN
            if (MlmeIndication.MlmeIndication == MLME_SCHEDULE_UPLINK) {
                McpsIndication.UplinkNeeded = false;
            }
#endif
        }

        // Verify if sticky MAC commands are pending or not
        if (IsStickyMacCommandPending() == true) {  // Setup MLME indication
            SetMlmeScheduleUplinkIndication();
            LoRaMacPrimitives->MacMlmeIndication(&MlmeIndication);
            LoRaMacFlags.Bits.MlmeInd = 0;
#ifdef CONFIG_LWAN
            if (MlmeIndication.MlmeIndication == MLME_SCHEDULE_UPLINK) {
                McpsIndication.UplinkNeeded = false;
            }
#endif
        }

        // Procedure done. Reset variables.
        LoRaMacFlags.Bits.MacDone = 0;

        LoRaMacClassBResumeBeaconing();

        if (LoRaMacDeviceClass == CLASS_C) {  // Activate RX2 window for Class C
            OpenContinuousRx2Window();
        }
    } else {
        // Operation not finished restart timer
        TimerSetValue(&MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT);
        TimerStart(&MacStateCheckTimer);
    }

    // Handle MCPS indication
    if (LoRaMacFlags.Bits.McpsInd == 1) {
        LoRaMacFlags.Bits.McpsInd = 0;
        if (LoRaMacFlags.Bits.McpsIndSkip == 0) {
            LoRaMacPrimitives->MacMcpsIndication(&McpsIndication);
        }
        LoRaMacFlags.Bits.McpsIndSkip = 0;
    }
}

static void OnTxDelayedTimerEvent(void)
{
    LoRaMacHeader_t macHdr;
    LoRaMacFrameCtrl_t fCtrl;
    AlternateDrParams_t altDr;

    TimerStop(&TxDelayedTimer);
    LoRaMacState &= ~LORAMAC_TX_DELAYED;

    if ((LoRaMacFlags.Bits.MlmeReq == 1) && (LoRaMacConfirmQueueIsCmdActive(MLME_JOIN) == true)) {
        ResetMacParameters();

        altDr.NbTrials = JoinRequestTrials + 1;
#ifdef CONFIG_LINKWAN
        altDr.joinmethod = LoRaMacParams.method;
        altDr.datarate = LoRaMacParams.ChannelsDatarate;
#endif
        LoRaMacParams.ChannelsDatarate = RegionAlternateDr(LoRaMacRegion, &altDr);

        macHdr.Value = 0;
        macHdr.Bits.MType = FRAME_TYPE_JOIN_REQ;

        fCtrl.Value = 0;
        fCtrl.Bits.Adr = AdrCtrlOn;

        /* In case of join request retransmissions, the stack must prepare
         * the frame again, because the network server keeps track of the random
         * LoRaMacDevNonce values to prevent reply attacks. */
        PrepareFrame(&macHdr, &fCtrl, 0, NULL, 0);
    }

    ScheduleTx();
}

#ifdef CONFIG_LORA_CAD
static void OnTxImmediateTimerEvent(void)
{
    TimerStop(&TxImmediateTimer);
    SendFrameOnChannel(Channel);
}
#endif

static void OnRxWindow1TimerEvent(void)
{
    TimerStop(&RxWindowTimer1);
    RxSlot = RX_SLOT_WIN_1;

    RxWindow1Config.Channel = Channel;
    RxWindow1Config.DrOffset = LoRaMacParams.Rx1DrOffset;
    RxWindow1Config.DownlinkDwellTime = LoRaMacParams.DownlinkDwellTime;
    RxWindow1Config.RepeaterSupport = LoRaMacParams.RepeaterSupport;
    RxWindow1Config.RxContinuous = false;
    RxWindow1Config.RxSlot = RxSlot;

    if (LoRaMacDeviceClass == CLASS_C) {
        Radio.Standby();
    }

    RegionRxConfig(LoRaMacRegion, &RxWindow1Config, (int8_t*)&McpsIndication.RxDatarate);
    RxWindowSetup(RxWindow1Config.RxContinuous, LoRaMacParams.MaxRxWindow);
}

static void OnRxWindow2TimerEvent(void)
{
    TimerStop(&RxWindowTimer2);

    RxWindow2Config.Channel = Channel;
    RxWindow2Config.Datarate = LoRaMacParams.Rx2Channel.Datarate;
    RxWindow2Config.Frequency = LoRaMacParams.Rx2Channel.Frequency;
    RxWindow2Config.DownlinkDwellTime = LoRaMacParams.DownlinkDwellTime;
    RxWindow2Config.RepeaterSupport = LoRaMacParams.RepeaterSupport;
    RxWindow2Config.RxSlot = RX_SLOT_WIN_2;

    if (LoRaMacDeviceClass != CLASS_C) {
        RxWindow2Config.RxContinuous = false;
    } else {
        RxWindow2Config.RxContinuous = true;
    }

    if (RegionRxConfig(LoRaMacRegion, &RxWindow2Config, (int8_t*)&McpsIndication.RxDatarate) == true) {
        RxWindowSetup(RxWindow2Config.RxContinuous, LoRaMacParams.MaxRxWindow);
        RxSlot = RX_SLOT_WIN_2;
    }
}

static void CheckToDisableAckTimeout(bool nodeAckRequested,
                                     DeviceClass_t devClass,
                                     bool ackReceived,
                                     uint8_t ackTimeoutRetriesCounter,
                                     uint8_t ackTimeoutRetries)
{
    // There are three cases where we need to stop the AckTimeoutTimer:
    if (nodeAckRequested == false) {
        if (devClass == CLASS_C) {  // FIRST CASE
            // We have performed an unconfirmed uplink in class c mode
            // and have received a downlink in RX1 or RX2.
            TimerStop(&AckTimeoutTimer);
        }
    } else {
        if (ackReceived == 1) {  // SECOND CASE
            // We have performed a confirmed uplink and have received a
            // downlink with a valid ACK.
            TimerStop(&AckTimeoutTimer);
        } else {  // THIRD CASE
            if (ackTimeoutRetriesCounter > ackTimeoutRetries) {
                // We have performed a confirmed uplink and have not
                // received a downlink with a valid ACK. In this case
                // we need to verify if the maximum retries have been
                // elapsed. If so, stop the timer.
                TimerStop(&AckTimeoutTimer);
            }
        }
    }
}

static void OnAckTimeoutTimerEvent(void)
{
    TimerStop(&AckTimeoutTimer);

    if (NodeAckRequested == true) {
        AckTimeoutRetry = true;
        LoRaMacState &= ~LORAMAC_ACK_REQ;
    }
    if (LoRaMacDeviceClass == CLASS_C) {
        LoRaMacFlags.Bits.MacDone = 1;
    }
}

static void RxWindowSetup(bool rxContinuous, uint32_t maxRxWindow)
{
    if (rxContinuous == false) {
        Radio.Rx(maxRxWindow);
    } else {
        Radio.Rx(0);  // Continuous mode
    }
}

static LoRaMacStatus_t SwitchClass(DeviceClass_t deviceClass)
{
    LoRaMacStatus_t status = LORAMAC_STATUS_PARAMETER_INVALID;

    switch (LoRaMacDeviceClass) {
        case CLASS_A: {
            if (deviceClass == CLASS_B) {
                status = LoRaMacClassBSwitchClass(deviceClass);
                if (status == LORAMAC_STATUS_OK) {
                    LoRaMacDeviceClass = deviceClass;
                }
            }

            if (deviceClass == CLASS_C) {
                LoRaMacDeviceClass = deviceClass;

                // Set the NodeAckRequested indicator to default
                NodeAckRequested = false;
                // Set the radio into sleep mode in case we are still in RX mode
                Radio.Sleep();
                // Compute Rx2 windows parameters in case the RX2 datarate has changed
                RegionComputeRxWindowParameters(LoRaMacRegion, LoRaMacParams.Rx2Channel.Datarate,
                                                LoRaMacParams.MinRxSymbols, LoRaMacParams.SystemMaxRxError,
                                                &RxWindow2Config);
                OpenContinuousRx2Window();

                status = LORAMAC_STATUS_OK;
            }
            break;
        }
        case CLASS_B: {
            status = LoRaMacClassBSwitchClass(deviceClass);
            if (status == LORAMAC_STATUS_OK) {
                LoRaMacDeviceClass = deviceClass;
            }
            break;
        }
        case CLASS_C: {
            if (deviceClass == CLASS_A) {
                LoRaMacDeviceClass = deviceClass;

                // Set the radio into sleep to setup a defined state
                Radio.Sleep();

                status = LORAMAC_STATUS_OK;
            }
            break;
        }
    }

    return status;
}

static bool ValidatePayloadLength(uint8_t lenN, int8_t datarate, uint8_t fOptsLen)
{
    GetPhyParams_t getPhy;
    PhyParam_t phyParam;
    uint16_t maxN = 0;
    uint16_t payloadSize = 0;

    // Setup PHY request
    getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;
    getPhy.Datarate = datarate;
    getPhy.Attribute = PHY_MAX_PAYLOAD;

    // Get the maximum payload length
    if (LoRaMacParams.RepeaterSupport == true) {
        getPhy.Attribute = PHY_MAX_PAYLOAD_REPEATER;
    }
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    maxN = phyParam.Value;

    // Calculate the resulting payload size
    payloadSize = (lenN + fOptsLen);

    // Validation of the application payload size
    if ((((payloadSize > maxN) && (fOptsLen != 0) && (fOptsLen <= maxN)) || (payloadSize <= maxN)) &&
        (payloadSize <= LORAMAC_PHY_MAXPAYLOAD)) {
        return true;
    }
    return false;
}

static bool IsStickyMacCommandPending(void)
{
    if (MacCommandsBufferToRepeatIndex > 0) {
        // Sticky MAC commands pending
        return true;
    }
    return false;
}

static void SetMlmeScheduleUplinkIndication(void)
{
    MlmeIndication.MlmeIndication = MLME_SCHEDULE_UPLINK;
    LoRaMacFlags.Bits.MlmeInd = 1;
}

static LoRaMacStatus_t AddMacCommand(uint8_t cmd, uint8_t p1, uint8_t p2)
{
    LoRaMacStatus_t status = LORAMAC_STATUS_BUSY;
    // The maximum buffer length must take MAC commands to re-send into account.
    uint8_t bufLen = LORA_MAC_COMMAND_MAX_LENGTH - MacCommandsBufferToRepeatIndex;

    switch (cmd) {
        case MOTE_MAC_LINK_CHECK_REQ:
            if (MacCommandsBufferIndex < bufLen) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // No payload for this command
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_LINK_ADR_ANS:
            if (MacCommandsBufferIndex < (bufLen - 1)) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // Margin
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_DUTY_CYCLE_ANS:
            if (MacCommandsBufferIndex < bufLen) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // No payload for this answer
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_RX_PARAM_SETUP_ANS:
            if (MacCommandsBufferIndex < (bufLen - 1)) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // Status: Datarate ACK, Channel ACK
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                // This is a sticky MAC command answer. Setup indication
                SetMlmeScheduleUplinkIndication();
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_DEV_STATUS_ANS:
            if (MacCommandsBufferIndex < (bufLen - 2)) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // 1st byte Battery
                // 2nd byte Margin
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                MacCommandsBuffer[MacCommandsBufferIndex++] = p2;
                SetMlmeScheduleUplinkIndication();
                status = LORAMAC_STATUS_OK;
                LOG_PRINTF(LL_VDEBUG, "ready to send MOTE_MAC_DEV_STATUS_ANS p1=%d p2=%d\r\n", p1, p2);
            }
            break;
        case MOTE_MAC_NEW_CHANNEL_ANS:
            if (MacCommandsBufferIndex < (bufLen - 1)) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // Status: Datarate range OK, Channel frequency OK
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_RX_TIMING_SETUP_ANS:
            if (MacCommandsBufferIndex < bufLen) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // No payload for this answer
                // This is a sticky MAC command answer. Setup indication
                SetMlmeScheduleUplinkIndication();
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_TX_PARAM_SETUP_ANS:
            if (MacCommandsBufferIndex < bufLen) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // No payload for this answer
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_DL_CHANNEL_ANS:
            if (MacCommandsBufferIndex < bufLen) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // Status: Uplink frequency exists, Channel frequency OK
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                // This is a sticky MAC command answer. Setup indication
                SetMlmeScheduleUplinkIndication();

                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_DEVICE_TIME_REQ:
            if (MacCommandsBufferIndex < LORA_MAC_COMMAND_MAX_LENGTH) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // No payload for this answer
                status = LORAMAC_STATUS_OK;
                LOG_PRINTF(LL_VDEBUG, "ready to send MOTE_MAC_DEVICE_TIME_REQ\r\n");
            }
            break;
        case MOTE_MAC_PING_SLOT_INFO_REQ:
            if (MacCommandsBufferIndex < (LORA_MAC_COMMAND_MAX_LENGTH - 1)) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // Status: Periodicity and Datarate
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                status = LORAMAC_STATUS_OK;
                LOG_PRINTF(LL_VDEBUG, "ready to send MOTE_MAC_PING_SLOT_INFO_REQ value=%d\r\n", p1);
            }
            break;
        case MOTE_MAC_PING_SLOT_FREQ_ANS:
            if (MacCommandsBufferIndex < LORA_MAC_COMMAND_MAX_LENGTH) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // Status: Datarate range OK, Channel frequency OK
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                SetMlmeScheduleUplinkIndication();
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_BEACON_TIMING_REQ:
            if (MacCommandsBufferIndex < LORA_MAC_COMMAND_MAX_LENGTH) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // No payload for this answer
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_BEACON_FREQ_ANS:
            if (MacCommandsBufferIndex < LORA_MAC_COMMAND_MAX_LENGTH) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // Status: Channel frequency OK
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                SetMlmeScheduleUplinkIndication();
                status = LORAMAC_STATUS_OK;
            }
            break;
        default:
            return LORAMAC_STATUS_SERVICE_UNKNOWN;
    }
    if (status == LORAMAC_STATUS_OK) {
        MacCommandsInNextTx = true;
        if (SrvAckRequested) {
            SetMlmeScheduleUplinkIndication();
        }
    }
    return status;
}

static uint8_t ParseMacCommandsToRepeat(uint8_t* cmdBufIn, uint8_t length, uint8_t* cmdBufOut)
{
    uint8_t i = 0;
    uint8_t cmdCount = 0;

    if ((cmdBufIn == NULL) || (cmdBufOut == NULL)) {
        return 0;
    }

    for (i = 0; i < length; i++) {
        switch (cmdBufIn[i]) {
            // STICKY
            case MOTE_MAC_DL_CHANNEL_ANS:
            case MOTE_MAC_RX_PARAM_SETUP_ANS: {
                // 1 byte payload
                cmdBufOut[cmdCount++] = cmdBufIn[i++];
                cmdBufOut[cmdCount++] = cmdBufIn[i];
                break;
            }
            case MOTE_MAC_RX_TIMING_SETUP_ANS: {
                // 0 byte payload
                cmdBufOut[cmdCount++] = cmdBufIn[i];
                break;
            }
            // NON-STICKY
            case MOTE_MAC_DEV_STATUS_ANS: {
                // 2 bytes payload
                i += 2;
                break;
            }
            case MOTE_MAC_LINK_ADR_ANS:
            case MOTE_MAC_NEW_CHANNEL_ANS: {
                // 1 byte payload
                i++;
                break;
            }
            case MOTE_MAC_TX_PARAM_SETUP_ANS:
            case MOTE_MAC_DUTY_CYCLE_ANS:
            case MOTE_MAC_LINK_CHECK_REQ: {
                // 0 byte payload
                break;
            }
            default:
                break;
        }
    }

    return cmdCount;
}

static void
ProcessMacCommands(uint8_t* payload, uint8_t macIndex, uint8_t commandsSize, uint8_t snr, LoRaMacRxSlot_t rxSlot)
{
    uint8_t status = 0;

    while (macIndex < commandsSize) {
        // Decode Frame MAC commands
        LOG_PRINTF(LL_VDEBUG, "MacCommands:%d being processed\r\n", payload[macIndex]);

        switch (payload[macIndex++]) {
            case SRV_MAC_LINK_CHECK_ANS:
                if (LoRaMacConfirmQueueIsCmdActive(MLME_LINK_CHECK) == true) {
                    LoRaMacConfirmQueueSetStatus(LORAMAC_EVENT_INFO_STATUS_OK, MLME_LINK_CHECK);
                    MlmeConfirm.DemodMargin = payload[macIndex++];
                    MlmeConfirm.NbGateways = payload[macIndex++];
#ifdef CONFIG_LWAN
                    McpsIndication.LinkCheckAnsReceived = true;
#endif
                }
                break;
            case SRV_MAC_LINK_ADR_REQ: {
                LinkAdrReqParams_t linkAdrReq;
                int8_t linkAdrDatarate = DR_0;
                int8_t linkAdrTxPower = TX_POWER_0;
                uint8_t linkAdrNbRep = 0;
                uint8_t linkAdrNbBytesParsed = 0;

                // Fill parameter structure
                linkAdrReq.Payload = &payload[macIndex - 1];
                linkAdrReq.PayloadSize = commandsSize - (macIndex - 1);
                linkAdrReq.AdrEnabled = AdrCtrlOn;
                linkAdrReq.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;
                linkAdrReq.CurrentDatarate = LoRaMacParams.ChannelsDatarate;
                linkAdrReq.CurrentTxPower = LoRaMacParams.ChannelsTxPower;
                linkAdrReq.CurrentNbRep = LoRaMacParams.ChannelsNbRep;

                // Process the ADR requests
                status = RegionLinkAdrReq(LoRaMacRegion, &linkAdrReq, &linkAdrDatarate, &linkAdrTxPower, &linkAdrNbRep,
                                          &linkAdrNbBytesParsed);
                if ((status & 0x07) == 0x07) {
#ifdef CONFIG_LWAN
                    if (LoRaMacParams.ChannelsNbRep != linkAdrNbRep) {
                        lwan_mac_config_set(MAC_CONFIG_UNCONF_NBTRIALS, (void*)&linkAdrNbRep);
                    }

                    if (LoRaMacParams.ChannelsDatarate != linkAdrDatarate) {
                        lwan_mac_config_set(MAC_CONFIG_DATARATE, (void*)&linkAdrDatarate);
                    }

                    if (LoRaMacParams.ChannelsTxPower != linkAdrTxPower) {
                        lwan_mac_config_set(MAC_CONFIG_TX_POWER, (void*)&linkAdrTxPower);
                    }
#endif
                    LoRaMacParams.ChannelsNbRep = linkAdrNbRep;
                    LoRaMacParams.ChannelsDatarate = linkAdrDatarate;
                    LoRaMacParams.ChannelsTxPower = linkAdrTxPower;
                }

                // Add the answers to the buffer
                for (uint8_t i = 0; i < (linkAdrNbBytesParsed / 5); i++) {
                    AddMacCommand(MOTE_MAC_LINK_ADR_ANS, status, 0);
                }
                // Update MAC index
                macIndex += linkAdrNbBytesParsed - 1;
            } break;
            case SRV_MAC_DUTY_CYCLE_REQ:
                MaxDCycle = payload[macIndex++];
                AggregatedDCycle = 1 << MaxDCycle;
                AddMacCommand(MOTE_MAC_DUTY_CYCLE_ANS, 0, 0);
                break;
            case SRV_MAC_RX_PARAM_SETUP_REQ: {
                RxParamSetupReqParams_t rxParamSetupReq;
                status = 0x07;

                rxParamSetupReq.DrOffset = (payload[macIndex] >> 4) & 0x07;
                rxParamSetupReq.Datarate = payload[macIndex] & 0x0F;
                macIndex++;

                rxParamSetupReq.Frequency = (uint32_t)payload[macIndex++];
                rxParamSetupReq.Frequency |= (uint32_t)payload[macIndex++] << 8;
                rxParamSetupReq.Frequency |= (uint32_t)payload[macIndex++] << 16;
                rxParamSetupReq.Frequency *= 100;

                // Perform request on region
                status = RegionRxParamSetupReq(LoRaMacRegion, &rxParamSetupReq);

                if ((status & 0x07) == 0x07) {
                    LoRaMacParams.Rx2Channel.Datarate = rxParamSetupReq.Datarate;
                    LoRaMacParams.Rx2Channel.Frequency = rxParamSetupReq.Frequency;
                    LoRaMacParams.Rx1DrOffset = rxParamSetupReq.DrOffset;
                }
                AddMacCommand(MOTE_MAC_RX_PARAM_SETUP_ANS, status, 0);
            } break;
            case SRV_MAC_DEV_STATUS_REQ: {
                uint8_t batteryLevel = BAT_LEVEL_NO_MEASURE;
                if ((LoRaMacCallbacks != NULL) && (LoRaMacCallbacks->GetBatteryLevel != NULL)) {
                    batteryLevel = LoRaMacCallbacks->GetBatteryLevel();
                }
                LOG_PRINTF(LL_VDEBUG, "receive SRV_MAC_DEV_STATUS_REQ\r\n");
                AddMacCommand(MOTE_MAC_DEV_STATUS_ANS, batteryLevel, snr);
                break;
            }
            case SRV_MAC_NEW_CHANNEL_REQ: {
                NewChannelReqParams_t newChannelReq;
                ChannelParams_t chParam;
                status = 0x03;

                newChannelReq.ChannelId = payload[macIndex++];
                newChannelReq.NewChannel = &chParam;

                chParam.Frequency = (uint32_t)payload[macIndex++];
                chParam.Frequency |= (uint32_t)payload[macIndex++] << 8;
                chParam.Frequency |= (uint32_t)payload[macIndex++] << 16;
                chParam.Frequency *= 100;
                chParam.Rx1Frequency = 0;
                chParam.DrRange.Value = payload[macIndex++];

                status = RegionNewChannelReq(LoRaMacRegion, &newChannelReq);

                AddMacCommand(MOTE_MAC_NEW_CHANNEL_ANS, status, 0);
            } break;
            case SRV_MAC_RX_TIMING_SETUP_REQ: {
                uint8_t delay = payload[macIndex++] & 0x0F;

                if (delay == 0) {
                    delay++;
                }
                LoRaMacParams.ReceiveDelay1 = delay * 1000;
                LoRaMacParams.ReceiveDelay2 = LoRaMacParams.ReceiveDelay1 + 1000;
                AddMacCommand(MOTE_MAC_RX_TIMING_SETUP_ANS, 0, 0);
            } break;
            case SRV_MAC_TX_PARAM_SETUP_REQ: {
                TxParamSetupReqParams_t txParamSetupReq;
                uint8_t eirpDwellTime = payload[macIndex++];

                txParamSetupReq.UplinkDwellTime = 0;
                txParamSetupReq.DownlinkDwellTime = 0;

                if ((eirpDwellTime & 0x20) == 0x20) {
                    txParamSetupReq.DownlinkDwellTime = 1;
                }
                if ((eirpDwellTime & 0x10) == 0x10) {
                    txParamSetupReq.UplinkDwellTime = 1;
                }
                txParamSetupReq.MaxEirp = eirpDwellTime & 0x0F;

                // Check the status for correctness
                if (RegionTxParamSetupReq(LoRaMacRegion, &txParamSetupReq) != -1) {
                    // Accept command
                    LoRaMacParams.UplinkDwellTime = txParamSetupReq.UplinkDwellTime;
                    LoRaMacParams.DownlinkDwellTime = txParamSetupReq.DownlinkDwellTime;
                    LoRaMacParams.MaxEirp = LoRaMacMaxEirpTable[txParamSetupReq.MaxEirp];
                    // Add command response
                    AddMacCommand(MOTE_MAC_TX_PARAM_SETUP_ANS, 0, 0);
                }
            } break;
            case SRV_MAC_DL_CHANNEL_REQ: {
                DlChannelReqParams_t dlChannelReq;
                status = 0x03;

                dlChannelReq.ChannelId = payload[macIndex++];
                dlChannelReq.Rx1Frequency = (uint32_t)payload[macIndex++];
                dlChannelReq.Rx1Frequency |= (uint32_t)payload[macIndex++] << 8;
                dlChannelReq.Rx1Frequency |= (uint32_t)payload[macIndex++] << 16;
                dlChannelReq.Rx1Frequency *= 100;

                status = RegionDlChannelReq(LoRaMacRegion, &dlChannelReq);

                AddMacCommand(MOTE_MAC_DL_CHANNEL_ANS, status, 0);
            } break;
            case SRV_MAC_DEVICE_TIME_ANS: {
                TimerTime_t currentTime = 0;
                TimerSysTime_t sysTimeAns = {0};
                TimerSysTime_t sysTime = {0};
                TimerSysTime_t sysTimeCurrent = {0};

                sysTimeAns.Seconds = (uint32_t)payload[macIndex++];
                sysTimeAns.Seconds |= (uint32_t)payload[macIndex++] << 8;
                sysTimeAns.Seconds |= (uint32_t)payload[macIndex++] << 16;
                sysTimeAns.Seconds |= (uint32_t)payload[macIndex++] << 24;
                sysTimeAns.SubSeconds = payload[macIndex++];

                // Convert the fractional second received in ms
                // round( pow( 0.5, 8.0 ) * 1000 ) = 3.90625
                sysTimeAns.SubSeconds = sysTimeAns.SubSeconds * 3.90625;

                // Add Unix to Gps epcoh offset. The system time is based on Unix time.
                sysTimeAns.Seconds += UNIX_GPS_EPOCH_OFFSET;

                // Compensate time difference between Tx Done time and now
                sysTimeCurrent = TimerGetSysTime();

                sysTime = TimerAddSysTime(sysTimeCurrent, TimerSubSysTime(sysTimeAns, LastTxSysTime));
                LOG_PRINTF(LL_VDEBUG, "receive SRV_MAC_DEVICE_TIME_ANS, set time=%u.%d\r\n",
                           (unsigned int)sysTime.Seconds, sysTime.SubSeconds);
                // Apply the new system time.
                TimerSetSysTime(sysTime);
                currentTime = TimerGetCurrentTime();

                LoRaMacClassBDeviceTimeAns(currentTime);
#ifdef CONFIG_LWAN
                McpsIndication.DevTimeAnsReceived = true;
#endif
            } break;
            case SRV_MAC_PING_SLOT_INFO_ANS: {
                // According to the specification, it is not allowed to process this answer in
                // a ping or multicast slot
                if ((rxSlot != RX_SLOT_WIN_PING_SLOT) && (rxSlot != RX_SLOT_WIN_MULTICAST_SLOT)) {
                    LOG_PRINTF(LL_VDEBUG, "receive SRV_MAC_PING_SLOT_INFO_ANS in RX1\r\n");
                    LoRaMacClassBPingSlotInfoAns();
                }
            } break;
            case SRV_MAC_PING_SLOT_CHANNEL_REQ: {
                uint8_t status = 0x03;
                uint32_t frequency = 0;
                uint8_t datarate;

                frequency = (uint32_t)payload[macIndex++];
                frequency |= (uint32_t)payload[macIndex++] << 8;
                frequency |= (uint32_t)payload[macIndex++] << 16;
                frequency *= 100;
                datarate = payload[macIndex++] & 0x0F;
                LOG_PRINTF(LL_VDEBUG, "receive SRV_MAC_PING_SLOT_CHANNEL_REQ frequency=%u datarate=%u\r\n",
                           (unsigned int)frequency, datarate);
                status = LoRaMacClassBPingSlotChannelReq(datarate, frequency);
                AddMacCommand(MOTE_MAC_PING_SLOT_FREQ_ANS, status, 0);
            } break;
            case SRV_MAC_BEACON_TIMING_ANS: {
                uint16_t beaconTimingDelay = 0;
                uint8_t beaconTimingChannel = 0;

                beaconTimingDelay = (uint16_t)payload[macIndex++];
                beaconTimingDelay |= (uint16_t)payload[macIndex++] << 8;
                beaconTimingChannel = payload[macIndex++];

                LoRaMacClassBBeaconTimingAns(beaconTimingDelay, beaconTimingChannel);
            } break;
            case SRV_MAC_BEACON_FREQ_REQ: {
                uint32_t frequency = 0;

                frequency = (uint32_t)payload[macIndex++];
                frequency |= (uint32_t)payload[macIndex++] << 8;
                frequency |= (uint32_t)payload[macIndex++] << 16;
                frequency *= 100;
                LOG_PRINTF(LL_VDEBUG, "receive SRV_MAC_BEACON_FREQ_REQ frequency=%u\r\n", (unsigned int)frequency);
                if (LoRaMacClassBBeaconFreqReq(frequency) == true) {
                    AddMacCommand(MOTE_MAC_BEACON_FREQ_ANS, 1, 0);
                } else {
                    AddMacCommand(MOTE_MAC_BEACON_FREQ_ANS, 0, 0);
                }
            } break;
            default:
                // Unknown command. ABORT MAC commands processing
                return;
        }
    }
}

#ifdef CONFIG_LORA_CAD
static bool StartCAD(uint8_t channel)
{
    TxConfigParams_t txConfig;
    int8_t txPower = 0;
    TimerTime_t txTime = 0;

    memset(&txConfig, 0, sizeof(TxConfigParams_t));
    txConfig.Channel = channel;
    txConfig.Datarate = LoRaMacParams.ChannelsDatarate;
    txConfig.TxPower = LoRaMacParams.ChannelsTxPower;
    txConfig.MaxEirp = LoRaMacParams.MaxEirp;
    txConfig.AntennaGain = LoRaMacParams.AntennaGain;
    txConfig.PktLen = LoRaMacBufferPktLen;

    bool ret = RegionTxConfig(LoRaMacRegion, &txConfig, &txPower, &txTime);
    Radio.StartCad(LORA_CAD_SYMBOLS);

    return ret;
}
#endif

LoRaMacStatus_t Send(LoRaMacHeader_t* macHdr, uint8_t fPort, void* fBuffer, uint16_t fBufferSize)
{
    LoRaMacFrameCtrl_t fCtrl;
    LoRaMacStatus_t status = LORAMAC_STATUS_PARAMETER_INVALID;
#ifdef CONFIG_LWAN
    lwan_dev_status_set(DEVICE_STATUS_IDLE);
#endif
    fCtrl.Value = 0;
    fCtrl.Bits.FOptsLen = 0;
    if (LoRaMacDeviceClass == CLASS_B) {
        LOG_PRINTF(LL_VDEBUG, "Send class b frame\r\n");
        fCtrl.Bits.FPending = 1;
    } else {
        fCtrl.Bits.FPending = 0;
    }
    fCtrl.Bits.Ack = false;
    fCtrl.Bits.AdrAckReq = false;
    fCtrl.Bits.Adr = AdrCtrlOn;

    // Prepare the frame
    status = PrepareFrame(macHdr, &fCtrl, fPort, fBuffer, fBufferSize);

    // Validate status
    if (status != LORAMAC_STATUS_OK) {
        return status;
    }

    // Reset confirm parameters
    McpsConfirm.NbRetries = 0;
    McpsConfirm.AckReceived = false;
    McpsConfirm.UpLinkCounter = UpLinkCounter;

    status = ScheduleTx();
    LOG_PRINTF(LL_VDEBUG, "MacHdr major:%d rfu:%d mtype:%d\r\n", macHdr->Bits.Major, macHdr->Bits.RFU,
               macHdr->Bits.MType);
    LOG_PRINTF(LL_VDEBUG, "fCtrl rfu:%d Adr:%d AdrAckReq:%d fCtrl.Bits.Ack:%d\r\n", fCtrl.Bits.FPending, fCtrl.Bits.Adr,
               fCtrl.Bits.AdrAckReq, fCtrl.Bits.Ack);

    return status;
}

static LoRaMacStatus_t ScheduleTx(void)
{
    TimerTime_t dutyCycleTimeOff = 0;
    NextChanParams_t nextChan;

    // Check if the device is off
    if (MaxDCycle == 255) {
        return LORAMAC_STATUS_DEVICE_OFF;
    }
    if (MaxDCycle == 0) {
        AggregatedTimeOff = 0;
    }

    // Update Backoff
    CalculateBackOff(LastTxChannel);

    nextChan.AggrTimeOff = AggregatedTimeOff;
    nextChan.Datarate = LoRaMacParams.ChannelsDatarate;
    nextChan.DutyCycleEnabled = DutyCycleOn;
    nextChan.Joined = IsLoRaMacNetworkJoined;
    nextChan.LastAggrTx = AggregatedLastTxDoneTime;
#ifdef CONFIG_LINKWAN
    nextChan.joinmethod = LoRaMacParams.method;
    nextChan.freqband = LoRaMacParams.freqband;
    nextChan.update_freqband = LoRaMacParams.update_freqband;
#endif

    // Select channel
    while (RegionNextChannel(LoRaMacRegion, &nextChan, &Channel, &dutyCycleTimeOff, &AggregatedTimeOff) == false) {
        // Set the default datarate
        LoRaMacParams.ChannelsDatarate = LoRaMacParamsDefaults.ChannelsDatarate;
        // Update datarate in the function parameters
        nextChan.Datarate = LoRaMacParams.ChannelsDatarate;
    }

#ifdef CONFIG_LINKWAN
    LoRaMacParams.freqband = nextChan.freqband;
    LoRaMacParams.update_freqband = nextChan.update_freqband;
#endif

    // Compute Rx1 windows parameters
    RegionComputeRxWindowParameters(LoRaMacRegion,
                                    RegionApplyDrOffset(LoRaMacRegion, LoRaMacParams.DownlinkDwellTime,
                                                        LoRaMacParams.ChannelsDatarate, LoRaMacParams.Rx1DrOffset),
                                    LoRaMacParams.MinRxSymbols, LoRaMacParams.SystemMaxRxError, &RxWindow1Config);
    // Compute Rx2 windows parameters
    RegionComputeRxWindowParameters(LoRaMacRegion, LoRaMacParams.Rx2Channel.Datarate, LoRaMacParams.MinRxSymbols,
                                    LoRaMacParams.SystemMaxRxError, &RxWindow2Config);

    if (IsLoRaMacNetworkJoined == false) {
        RxWindow1Delay = LoRaMacParams.JoinAcceptDelay1 + RxWindow1Config.WindowOffset;
        RxWindow2Delay = LoRaMacParams.JoinAcceptDelay2 + RxWindow2Config.WindowOffset;
    } else {
        if (ValidatePayloadLength(LoRaMacTxPayloadLen, LoRaMacParams.ChannelsDatarate, MacCommandsBufferIndex) ==
            false) {
            return LORAMAC_STATUS_LENGTH_ERROR;
        }
        RxWindow1Delay = LoRaMacParams.ReceiveDelay1 + RxWindow1Config.WindowOffset;
        RxWindow2Delay = LoRaMacParams.ReceiveDelay2 + RxWindow2Config.WindowOffset;
    }

    // Schedule transmission of frame
    if (dutyCycleTimeOff == 0) {
#ifdef CONFIG_LORA_CAD
        StartCAD(Channel);
        return LORAMAC_STATUS_OK;
#else
        // Try to send now
        return SendFrameOnChannel(Channel);
#endif
    } else {
        // Send later - prepare timer
        LoRaMacState |= LORAMAC_TX_DELAYED;
        TimerSetValue(&TxDelayedTimer, dutyCycleTimeOff);
        TimerStart(&TxDelayedTimer);

        return LORAMAC_STATUS_OK;
    }
}

static void CalculateBackOff(uint8_t channel)
{
    CalcBackOffParams_t calcBackOff;

    calcBackOff.Joined = IsLoRaMacNetworkJoined;
    calcBackOff.DutyCycleEnabled = DutyCycleOn;
    calcBackOff.Channel = channel;
    calcBackOff.ElapsedTime = TimerGetElapsedTime(LoRaMacInitializationTime);
    calcBackOff.TxTimeOnAir = TxTimeOnAir;
    calcBackOff.LastTxIsJoinRequest = LastTxIsJoinRequest;

    // Update regional back-off
    RegionCalcBackOff(LoRaMacRegion, &calcBackOff);

    // Update aggregated time-off
    AggregatedTimeOff = TxTimeOnAir * AggregatedDCycle - TxTimeOnAir;
}

static void ResetMacParameters(void)
{
    IsLoRaMacNetworkJoined = false;

    // Counters
    UpLinkCounter = 0;
    DownLinkCounter = 0;
    AdrAckCounter = 0;

    ChannelsNbRepCounter = 0;

    AckTimeoutRetries = 1;
    AckTimeoutRetriesCounter = 1;
    AckTimeoutRetry = false;

    MaxDCycle = 0;
    AggregatedDCycle = 1;

    MacCommandsBufferIndex = 0;
    MacCommandsBufferToRepeatIndex = 0;

    IsRxWindowsEnabled = true;

    LoRaMacParams.ChannelsTxPower = LoRaMacParamsDefaults.ChannelsTxPower;
    LoRaMacParams.ChannelsDatarate = LoRaMacParamsDefaults.ChannelsDatarate;
    LoRaMacParams.Rx1DrOffset = LoRaMacParamsDefaults.Rx1DrOffset;
    memcpy(&LoRaMacParams.Rx2Channel, &LoRaMacParamsDefaults.Rx2Channel, sizeof(LoRaMacParams.Rx2Channel));
    LoRaMacParams.UplinkDwellTime = LoRaMacParamsDefaults.UplinkDwellTime;
    LoRaMacParams.DownlinkDwellTime = LoRaMacParamsDefaults.DownlinkDwellTime;
    LoRaMacParams.MaxEirp = LoRaMacParamsDefaults.MaxEirp;
    LoRaMacParams.AntennaGain = LoRaMacParamsDefaults.AntennaGain;

    NodeAckRequested = false;
    SrvAckRequested = false;
    MacCommandsInNextTx = false;

    // Reset Multicast downlink counters
    MulticastParams_t* cur = MulticastChannels;
    while (cur != NULL) {
        cur->DownLinkCounter = 0;
        cur = cur->Next;
    }

    // Initialize channel index.
    Channel = 0;
    LastTxChannel = Channel;
}

static bool IsFPortAllowed(uint8_t fPort)
{
    if (fPort > 224) {
        return false;
    }
    return true;
}

static void OpenContinuousRx2Window(void)
{
    OnRxWindow2TimerEvent();
    RxSlot = RX_SLOT_WIN_CLASS_C;
}

LoRaMacStatus_t
PrepareFrame(LoRaMacHeader_t* macHdr, LoRaMacFrameCtrl_t* fCtrl, uint8_t fPort, void* fBuffer, uint16_t fBufferSize)
{
    AdrNextParams_t adrNext;
    uint16_t i;
    uint8_t pktHeaderLen = 0;
    uint32_t mic = 0;
    const void* payload = fBuffer;
    uint8_t framePort = fPort;

    LoRaMacBufferPktLen = 0;

    NodeAckRequested = false;

    if (fBuffer == NULL) {
        fBufferSize = 0;
    }

    LoRaMacTxPayloadLen = fBufferSize;

    LoRaMacBuffer[pktHeaderLen++] = macHdr->Value;

    LOG_PRINTF(LL_VDEBUG, "UpLoad frame %d being processed\r\n", macHdr->Bits.MType);

    switch (macHdr->Bits.MType) {
        case FRAME_TYPE_JOIN_REQ:
            LoRaMacBufferPktLen = pktHeaderLen;

            memcpyr(LoRaMacBuffer + LoRaMacBufferPktLen, LoRaMacAppEui, 8);
            LoRaMacBufferPktLen += 8;
            memcpyr(LoRaMacBuffer + LoRaMacBufferPktLen, LoRaMacDevEui, 8);
            LoRaMacBufferPktLen += 8;

            LoRaMacDevNonce = rand1();
            LOG_PRINTF(LL_VDEBUG, "DevNonce:%d\rn\n", LoRaMacDevNonce);

            LoRaMacBuffer[LoRaMacBufferPktLen++] = LoRaMacDevNonce & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen++] = (LoRaMacDevNonce >> 8) & 0xFF;

            LoRaMacJoinComputeMic(LoRaMacBuffer, LoRaMacBufferPktLen & 0xFF, LoRaMacAppKey, &mic);

            LoRaMacBuffer[LoRaMacBufferPktLen++] = mic & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen++] = (mic >> 8) & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen++] = (mic >> 16) & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen++] = (mic >> 24) & 0xFF;

            break;
        case FRAME_TYPE_DATA_CONFIRMED_UP:
            NodeAckRequested = true;
        // Intentional fallthrough
        case FRAME_TYPE_DATA_UNCONFIRMED_UP:
            if (IsLoRaMacNetworkJoined == false) {
                return LORAMAC_STATUS_NO_NETWORK_JOINED;  // No network has been joined yet
            }

            // Adr next request
            adrNext.UpdateChanMask = true;
            adrNext.AdrEnabled = fCtrl->Bits.Adr;
            adrNext.AdrAckCounter = AdrAckCounter;
            adrNext.Datarate = LoRaMacParams.ChannelsDatarate;
            adrNext.TxPower = LoRaMacParams.ChannelsTxPower;
            adrNext.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;

            fCtrl->Bits.AdrAckReq = RegionAdrNext(LoRaMacRegion, &adrNext, &LoRaMacParams.ChannelsDatarate,
                                                  &LoRaMacParams.ChannelsTxPower, &AdrAckCounter);

            if (SrvAckRequested == true) {
                SrvAckRequested = false;
                fCtrl->Bits.Ack = 1;
            }

            LoRaMacBuffer[pktHeaderLen++] = (LoRaMacDevAddr) & 0xFF;
            LoRaMacBuffer[pktHeaderLen++] = (LoRaMacDevAddr >> 8) & 0xFF;
            LoRaMacBuffer[pktHeaderLen++] = (LoRaMacDevAddr >> 16) & 0xFF;
            LoRaMacBuffer[pktHeaderLen++] = (LoRaMacDevAddr >> 24) & 0xFF;

            LoRaMacBuffer[pktHeaderLen++] = fCtrl->Value;

            LoRaMacBuffer[pktHeaderLen++] = UpLinkCounter & 0xFF;
            LoRaMacBuffer[pktHeaderLen++] = (UpLinkCounter >> 8) & 0xFF;

            // Copy the MAC commands which must be re-send into the MAC command buffer
            memcpy1(&MacCommandsBuffer[MacCommandsBufferIndex], MacCommandsBufferToRepeat,
                    MacCommandsBufferToRepeatIndex);
            MacCommandsBufferIndex += MacCommandsBufferToRepeatIndex;

            if ((payload != NULL) && (LoRaMacTxPayloadLen > 0)) {
                if (MacCommandsInNextTx == true) {
                    if (MacCommandsBufferIndex <= LORA_MAC_COMMAND_MAX_FOPTS_LENGTH) {
                        fCtrl->Bits.FOptsLen += MacCommandsBufferIndex;

                        // Update FCtrl field with new value of OptionsLength
                        LoRaMacBuffer[0x05] = fCtrl->Value;
                        for (i = 0; i < MacCommandsBufferIndex; i++) {
                            LoRaMacBuffer[pktHeaderLen++] = MacCommandsBuffer[i];
                        }
                    } else {
                        LoRaMacTxPayloadLen = MacCommandsBufferIndex;
                        payload = MacCommandsBuffer;
                        framePort = 0;
                    }
                }
            } else {
                if ((MacCommandsBufferIndex > 0) && (MacCommandsInNextTx == true)) {
                    LoRaMacTxPayloadLen = MacCommandsBufferIndex;
                    payload = MacCommandsBuffer;
                    framePort = 0;
                }
            }
            MacCommandsInNextTx = false;
            // Store MAC commands which must be re-send in case the device does not receive a downlink anymore
            MacCommandsBufferToRepeatIndex =
                ParseMacCommandsToRepeat(MacCommandsBuffer, MacCommandsBufferIndex, MacCommandsBufferToRepeat);
            if (MacCommandsBufferToRepeatIndex > 0) {
                MacCommandsInNextTx = true;
            }

            if ((payload != NULL) && (LoRaMacTxPayloadLen > 0)) {
                LoRaMacBuffer[pktHeaderLen++] = framePort;
                if ((pktHeaderLen + LoRaMacTxPayloadLen) > (LORAMAC_PHY_MAXPAYLOAD - 4)) {
                    LoRaMacTxPayloadLen = LORAMAC_PHY_MAXPAYLOAD - 4 - pktHeaderLen;
                }

                if (framePort == 0) {
                    // Reset buffer index as the mac commands are being sent on port 0
                    MacCommandsBufferIndex = 0;
                    LoRaMacPayloadEncrypt((uint8_t*)payload, LoRaMacTxPayloadLen, LoRaMacNwkSKey, LoRaMacDevAddr,
                                          UP_LINK, UpLinkCounter, &LoRaMacBuffer[pktHeaderLen]);
                } else {
                    LoRaMacPayloadEncrypt((uint8_t*)payload, LoRaMacTxPayloadLen, LoRaMacAppSKey, LoRaMacDevAddr,
                                          UP_LINK, UpLinkCounter, &LoRaMacBuffer[pktHeaderLen]);
                }
            }
            LoRaMacBufferPktLen = pktHeaderLen + LoRaMacTxPayloadLen;

            LoRaMacComputeMic(LoRaMacBuffer, LoRaMacBufferPktLen, LoRaMacNwkSKey, LoRaMacDevAddr, UP_LINK,
                              UpLinkCounter, &mic);

            LoRaMacBuffer[LoRaMacBufferPktLen + 0] = mic & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen + 1] = (mic >> 8) & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen + 2] = (mic >> 16) & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen + 3] = (mic >> 24) & 0xFF;

            LoRaMacBufferPktLen += LORAMAC_MFR_LEN;

            break;
        case FRAME_TYPE_PROPRIETARY:
            if ((fBuffer != NULL) && (LoRaMacTxPayloadLen > 0)) {
                memcpy1(LoRaMacBuffer + pktHeaderLen, (uint8_t*)fBuffer, LoRaMacTxPayloadLen);
                LoRaMacBufferPktLen = pktHeaderLen + LoRaMacTxPayloadLen;
            }
            break;
        default:
            return LORAMAC_STATUS_SERVICE_UNKNOWN;
    }

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t SendFrameOnChannel(uint8_t channel)
{
    TxConfigParams_t txConfig = {0};
    int8_t txPower = 0;

    txConfig.Channel = channel;
    txConfig.Datarate = LoRaMacParams.ChannelsDatarate;
    txConfig.TxPower = LoRaMacParams.ChannelsTxPower;
    txConfig.MaxEirp = LoRaMacParams.MaxEirp;
    txConfig.AntennaGain = LoRaMacParams.AntennaGain;
    txConfig.PktLen = LoRaMacBufferPktLen;

    if (LoRaMacClassBIsBeaconExpected() == true) {
        return LORAMAC_STATUS_BUSY_BEACON_RESERVED_TIME;
    }

    if (LoRaMacDeviceClass == CLASS_B) {
        if (LoRaMacClassBIsPingExpected() == true) {
            return LORAMAC_STATUS_BUSY_PING_SLOT_WINDOW_TIME;
        } else if (LoRaMacClassBIsMulticastExpected() == true) {
            return LORAMAC_STATUS_BUSY_PING_SLOT_WINDOW_TIME;
        } else {
            LoRaMacClassBStopRxSlots();
        }
    }
    RegionTxConfig(LoRaMacRegion, &txConfig, &txPower, &TxTimeOnAir);

    LoRaMacConfirmQueueSetStatusCmn(LORAMAC_EVENT_INFO_STATUS_ERROR);
    McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
    McpsConfirm.Datarate = LoRaMacParams.ChannelsDatarate;
    McpsConfirm.TxPower = txPower;
    McpsConfirm.Channel = channel;

    // Store the time on air
    McpsConfirm.TxTimeOnAir = TxTimeOnAir;
    MlmeConfirm.TxTimeOnAir = TxTimeOnAir;

    if (LoRaMacClassBIsBeaconModeActive() == true) {
        // Currently, the Time-On-Air can only be computed when the radion is configured with
        // the TX configuration
        TimerTime_t collisionTime = LoRaMacClassBIsUplinkCollision(TxTimeOnAir);

        if (collisionTime > 0) {
            return LORAMAC_STATUS_BUSY_UPLINK_COLLISION;
        }
    }

    LoRaMacClassBHaltBeaconing();

    // Starts the MAC layer status check timer
    TimerSetValue(&MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT);
    TimerStart(&MacStateCheckTimer);

    if (IsLoRaMacNetworkJoined == false) {
        JoinRequestTrials++;
    }
    // Send now
    Radio.Send(LoRaMacBuffer, LoRaMacBufferPktLen);

    LoRaMacState |= LORAMAC_TX_RUNNING;
#ifdef CONFIG_LWAN
    lwan_dev_status_set(DEVICE_STATUS_SENDING);
#endif
    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t SetTxContinuousWave(uint16_t timeout)
{
    ContinuousWaveParams_t continuousWave;

    continuousWave.Channel = Channel;
    continuousWave.Datarate = LoRaMacParams.ChannelsDatarate;
    continuousWave.TxPower = LoRaMacParams.ChannelsTxPower;
    continuousWave.MaxEirp = LoRaMacParams.MaxEirp;
    continuousWave.AntennaGain = LoRaMacParams.AntennaGain;
    continuousWave.Timeout = timeout;

    RegionSetContinuousWave(LoRaMacRegion, &continuousWave);

    // Starts the MAC layer status check timer
    TimerSetValue(&MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT);
    TimerStart(&MacStateCheckTimer);

    LoRaMacState |= LORAMAC_TX_RUNNING;

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t SetTxContinuousWave1(uint16_t timeout, uint32_t frequency, uint8_t power)
{
    Radio.SetTxContinuousWave(frequency, power, timeout);

    // Starts the MAC layer status check timer
    TimerSetValue(&MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT);
    TimerStart(&MacStateCheckTimer);

    LoRaMacState |= LORAMAC_TX_RUNNING;

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t
LoRaMacInitialization(LoRaMacPrimitives_t* primitives, LoRaMacCallback_t* callbacks, LoRaMacRegion_t region)
{
    GetPhyParams_t getPhy;
    PhyParam_t phyParam;
    LoRaMacClassBCallback_t classBCallbacks;
    LoRaMacClassBParams_t classBParams;

    if (primitives == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    if ((primitives->MacMcpsConfirm == NULL) || (primitives->MacMcpsIndication == NULL) ||
        (primitives->MacMlmeConfirm == NULL) || (primitives->MacMlmeIndication == NULL)) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    // Verify if the region is supported
    if (RegionIsActive(region) == false) {
        return LORAMAC_STATUS_REGION_NOT_SUPPORTED;
    }

    // Confirm queue reset
    LoRaMacConfirmQueueInit(primitives);

    LoRaMacPrimitives = primitives;
    LoRaMacCallbacks = callbacks;
    LoRaMacRegion = region;

    LoRaMacFlags.Value = 0;

    LoRaMacDeviceClass = CLASS_A;
    LoRaMacState = LORAMAC_IDLE;

    JoinRequestTrials = 0;
    MaxJoinRequestTrials = 1;

    // Reset duty cycle times
    AggregatedLastTxDoneTime = 0;
    AggregatedTimeOff = 0;

    // Reset to defaults
    getPhy.Attribute = PHY_DUTY_CYCLE;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    DutyCycleOn = (bool)phyParam.Value;

    getPhy.Attribute = PHY_DEF_TX_POWER;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.ChannelsTxPower = phyParam.Value;

    getPhy.Attribute = PHY_DEF_TX_DR;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.ChannelsDatarate = phyParam.Value;

    getPhy.Attribute = PHY_MAX_RX_WINDOW;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.MaxRxWindow = phyParam.Value;

    getPhy.Attribute = PHY_RECEIVE_DELAY1;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.ReceiveDelay1 = phyParam.Value;

    getPhy.Attribute = PHY_RECEIVE_DELAY2;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.ReceiveDelay2 = phyParam.Value;

    getPhy.Attribute = PHY_JOIN_ACCEPT_DELAY1;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.JoinAcceptDelay1 = phyParam.Value;

    getPhy.Attribute = PHY_JOIN_ACCEPT_DELAY2;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.JoinAcceptDelay2 = phyParam.Value;

    getPhy.Attribute = PHY_DEF_DR1_OFFSET;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.Rx1DrOffset = phyParam.Value;

    getPhy.Attribute = PHY_DEF_RX2_FREQUENCY;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.Rx2Channel.Frequency = phyParam.Value;

    getPhy.Attribute = PHY_DEF_RX2_DR;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.Rx2Channel.Datarate = phyParam.Value;

    getPhy.Attribute = PHY_DEF_UPLINK_DWELL_TIME;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.UplinkDwellTime = phyParam.Value;

    getPhy.Attribute = PHY_DEF_DOWNLINK_DWELL_TIME;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.DownlinkDwellTime = phyParam.Value;

    getPhy.Attribute = PHY_DEF_MAX_EIRP;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.MaxEirp = phyParam.fValue;

    getPhy.Attribute = PHY_DEF_ANTENNA_GAIN;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.AntennaGain = phyParam.fValue;

    RegionInitDefaults(LoRaMacRegion, INIT_TYPE_INIT);

    // Init parameters which are not set in function ResetMacParameters
    LoRaMacParams.RepeaterSupport = false;
    LoRaMacParamsDefaults.ChannelsNbRep = 1;
    LoRaMacParamsDefaults.SystemMaxRxError = 10;
    LoRaMacParamsDefaults.MinRxSymbols = 6;

    LoRaMacParams.SystemMaxRxError = LoRaMacParamsDefaults.SystemMaxRxError;
    LoRaMacParams.MinRxSymbols = LoRaMacParamsDefaults.MinRxSymbols;
    LoRaMacParams.MaxRxWindow = LoRaMacParamsDefaults.MaxRxWindow;
    LoRaMacParams.ReceiveDelay1 = LoRaMacParamsDefaults.ReceiveDelay1;
    LoRaMacParams.ReceiveDelay2 = LoRaMacParamsDefaults.ReceiveDelay2;
    LoRaMacParams.JoinAcceptDelay1 = LoRaMacParamsDefaults.JoinAcceptDelay1;
    LoRaMacParams.JoinAcceptDelay2 = LoRaMacParamsDefaults.JoinAcceptDelay2;
    LoRaMacParams.ChannelsNbRep = LoRaMacParamsDefaults.ChannelsNbRep;

    ResetMacParameters();

    // Initialize timers
    TimerInit(&MacStateCheckTimer, OnMacStateCheckTimerEvent);
    TimerSetValue(&MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT);

    TimerInit(&TxDelayedTimer, OnTxDelayedTimerEvent);
    TimerInit(&RxWindowTimer1, OnRxWindow1TimerEvent);
    TimerInit(&RxWindowTimer2, OnRxWindow2TimerEvent);
    TimerInit(&AckTimeoutTimer, OnAckTimeoutTimerEvent);
#ifdef CONFIG_LORA_CAD
    TimerInit(&TxImmediateTimer, OnTxImmediateTimerEvent);
#endif

    // Store the current initialization time
    LoRaMacInitializationTime = TimerGetCurrentTime();

    // Initialize Radio driver
    RadioEvents.TxDone = OnRadioTxDone;
    RadioEvents.RxDone = OnRadioRxDone;
    RadioEvents.RxError = OnRadioRxError;
    RadioEvents.TxTimeout = OnRadioTxTimeout;
    RadioEvents.RxTimeout = OnRadioRxTimeout;
#ifdef CONFIG_LORA_CAD
    RadioEvents.CadDone = OnRadioCadDone;
#endif
    Radio.Init(&RadioEvents);

    // Random seed initialization
    srand1(Radio.Random());

    PublicNetwork = true;
    Radio.SetPublicNetwork(true);
    Radio.Sleep();

    // Initialize class b
    // Apply callback
    classBCallbacks.GetTemperatureLevel = NULL;
    if (callbacks != NULL) {
        classBCallbacks.GetTemperatureLevel = callbacks->GetTemperatureLevel;
    }

    // Must all be static. Don't use local references.
    classBParams.MlmeIndication = &MlmeIndication;
    classBParams.McpsIndication = &McpsIndication;
    classBParams.MlmeConfirm = &MlmeConfirm;
    classBParams.LoRaMacFlags = &LoRaMacFlags;
    classBParams.LoRaMacDevAddr = &LoRaMacDevAddr;
    classBParams.LoRaMacRegion = &LoRaMacRegion;
    classBParams.MacStateCheckTimer = &MacStateCheckTimer;
    classBParams.LoRaMacParams = &LoRaMacParams;
    classBParams.MulticastChannels = &MulticastChannels;

    LoRaMacClassBInit(&classBParams, &classBCallbacks);

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacQueryTxPossible(uint8_t size, LoRaMacTxInfo_t* txInfo)
{
    AdrNextParams_t adrNext;
    GetPhyParams_t getPhy;
    PhyParam_t phyParam;
    int8_t datarate = LoRaMacParamsDefaults.ChannelsDatarate;
    int8_t txPower = LoRaMacParamsDefaults.ChannelsTxPower;
    uint8_t fOptLen = MacCommandsBufferIndex + MacCommandsBufferToRepeatIndex;

    if (txInfo == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    // Setup ADR request
    adrNext.UpdateChanMask = false;
    adrNext.AdrEnabled = AdrCtrlOn;
    adrNext.AdrAckCounter = AdrAckCounter;
    adrNext.Datarate = LoRaMacParams.ChannelsDatarate;
    adrNext.TxPower = LoRaMacParams.ChannelsTxPower;
    adrNext.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;

    // We call the function for information purposes only. We don't want to
    // apply the datarate, the tx power and the ADR ack counter.
    RegionAdrNext(LoRaMacRegion, &adrNext, &datarate, &txPower, &AdrAckCounter);

    // Setup PHY request
    getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;
    getPhy.Datarate = datarate;
    getPhy.Attribute = PHY_MAX_PAYLOAD;

    // Change request in case repeater is supported
    if (LoRaMacParams.RepeaterSupport == true) {
        getPhy.Attribute = PHY_MAX_PAYLOAD_REPEATER;
    }
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    txInfo->CurrentPayloadSize = phyParam.Value;

    // Verify if the fOpts fit into the maximum payload
    if (txInfo->CurrentPayloadSize >= fOptLen) {
        txInfo->MaxPossiblePayload = txInfo->CurrentPayloadSize - fOptLen;
    } else {
        txInfo->MaxPossiblePayload = txInfo->CurrentPayloadSize;
        // The fOpts don't fit into the maximum payload. Omit the MAC commands to
        // ensure that another uplink is possible.
        fOptLen = 0;
        MacCommandsBufferIndex = 0;
        MacCommandsBufferToRepeatIndex = 0;
    }

    // Verify if the fOpts and the payload fit into the maximum payload
    if (ValidatePayloadLength(size, datarate, fOptLen) == false) {
        return LORAMAC_STATUS_LENGTH_ERROR;
    }
    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacMibGetRequestConfirm(MibRequestConfirm_t* mibGet)
{
    LoRaMacStatus_t status = LORAMAC_STATUS_OK;
    GetPhyParams_t getPhy;
    PhyParam_t phyParam;

    if (mibGet == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    switch (mibGet->Type) {
        case MIB_DEVICE_CLASS: {
            mibGet->Param.Class = LoRaMacDeviceClass;
            break;
        }
        case MIB_NETWORK_JOINED: {
            mibGet->Param.IsNetworkJoined = IsLoRaMacNetworkJoined;
            break;
        }
        case MIB_ADR: {
            mibGet->Param.AdrEnable = AdrCtrlOn;
            break;
        }
        case MIB_NET_ID: {
            mibGet->Param.NetID = LoRaMacNetID;
            break;
        }
        case MIB_DEV_ADDR: {
            mibGet->Param.DevAddr = LoRaMacDevAddr;
            break;
        }
        case MIB_NWK_SKEY: {
            mibGet->Param.NwkSKey = LoRaMacNwkSKey;
            break;
        }
        case MIB_APP_SKEY: {
            mibGet->Param.AppSKey = LoRaMacAppSKey;
            break;
        }
        case MIB_PUBLIC_NETWORK: {
            mibGet->Param.EnablePublicNetwork = PublicNetwork;
            break;
        }
        case MIB_REPEATER_SUPPORT: {
            mibGet->Param.EnableRepeaterSupport = LoRaMacParams.RepeaterSupport;
            break;
        }
        case MIB_CHANNELS: {
            getPhy.Attribute = PHY_CHANNELS;
            phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);

            mibGet->Param.ChannelList = phyParam.Channels;
            break;
        }
        case MIB_RX2_CHANNEL: {
            mibGet->Param.Rx2Channel = LoRaMacParams.Rx2Channel;
            break;
        }
        case MIB_RX2_DEFAULT_CHANNEL: {
            mibGet->Param.Rx2Channel = LoRaMacParamsDefaults.Rx2Channel;
            break;
        }
        case MIB_CHANNELS_DEFAULT_MASK: {
            getPhy.Attribute = PHY_CHANNELS_DEFAULT_MASK;
            phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);

            mibGet->Param.ChannelsDefaultMask = phyParam.ChannelsMask;
            break;
        }
        case MIB_CHANNELS_MASK: {
            getPhy.Attribute = PHY_CHANNELS_MASK;
            phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);

            mibGet->Param.ChannelsMask = phyParam.ChannelsMask;
            break;
        }
        case MIB_CHANNELS_NB_REP: {
            mibGet->Param.ChannelNbRep = LoRaMacParams.ChannelsNbRep;
            break;
        }
        case MIB_MAX_RX_WINDOW_DURATION: {
            mibGet->Param.MaxRxWindow = LoRaMacParams.MaxRxWindow;
            break;
        }
        case MIB_RECEIVE_DELAY_1: {
            mibGet->Param.ReceiveDelay1 = LoRaMacParams.ReceiveDelay1;
            break;
        }
        case MIB_RECEIVE_DELAY_2: {
            mibGet->Param.ReceiveDelay2 = LoRaMacParams.ReceiveDelay2;
            break;
        }
        case MIB_JOIN_ACCEPT_DELAY_1: {
            mibGet->Param.JoinAcceptDelay1 = LoRaMacParams.JoinAcceptDelay1;
            break;
        }
        case MIB_JOIN_ACCEPT_DELAY_2: {
            mibGet->Param.JoinAcceptDelay2 = LoRaMacParams.JoinAcceptDelay2;
            break;
        }
        case MIB_CHANNELS_DEFAULT_DATARATE: {
            mibGet->Param.ChannelsDefaultDatarate = LoRaMacParamsDefaults.ChannelsDatarate;
            break;
        }
        case MIB_CHANNELS_DATARATE: {
            mibGet->Param.ChannelsDatarate = LoRaMacParams.ChannelsDatarate;
            break;
        }
        case MIB_CHANNELS_DEFAULT_TX_POWER: {
            mibGet->Param.ChannelsDefaultTxPower = LoRaMacParamsDefaults.ChannelsTxPower;
            break;
        }
        case MIB_CHANNELS_TX_POWER: {
            mibGet->Param.ChannelsTxPower = LoRaMacParams.ChannelsTxPower;
            break;
        }
        case MIB_UPLINK_COUNTER: {
            mibGet->Param.UpLinkCounter = UpLinkCounter;
            break;
        }
        case MIB_DOWNLINK_COUNTER: {
            mibGet->Param.DownLinkCounter = DownLinkCounter;
            break;
        }
        case MIB_MULTICAST_CHANNEL: {
            mibGet->Param.MulticastList = MulticastChannels;
            break;
        }
        case MIB_SYSTEM_MAX_RX_ERROR: {
            mibGet->Param.SystemMaxRxError = LoRaMacParams.SystemMaxRxError;
            break;
        }
        case MIB_MIN_RX_SYMBOLS: {
            mibGet->Param.MinRxSymbols = LoRaMacParams.MinRxSymbols;
            break;
        }
        case MIB_ANTENNA_GAIN: {
            mibGet->Param.AntennaGain = LoRaMacParams.AntennaGain;
            break;
        }
#ifdef CONFIG_LWAN
        case MIB_RX1_DATARATE_OFFSET: {
            mibGet->Param.Rx1DrOffset = LoRaMacParams.Rx1DrOffset;
            break;
        }
        case MIB_MAC_STATE: {
            mibGet->Param.LoRaMacState = LoRaMacState;
            break;
        }
#endif
#ifdef CONFIG_LINKWAN
        case MIB_FREQ_BAND: {
            mibGet->Param.freqband = LoRaMacParams.freqband;
            break;
        }
#endif
        default:
            if (LoRaMacDeviceClass == CLASS_B) {
                status = LoRaMacClassBMibGetRequestConfirm(mibGet);
            } else {
                status = LORAMAC_STATUS_SERVICE_UNKNOWN;
            }
            break;
    }

    return status;
}

LoRaMacStatus_t LoRaMacMibSetRequestConfirm(MibRequestConfirm_t* mibSet)
{
    LoRaMacStatus_t status = LORAMAC_STATUS_OK;
    ChanMaskSetParams_t chanMaskSet;
    VerifyParams_t verify;

    if (mibSet == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    if ((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) {
        return LORAMAC_STATUS_BUSY;
    }

    switch (mibSet->Type) {
        case MIB_DEVICE_CLASS: {
            status = SwitchClass(mibSet->Param.Class);
            break;
        }
        case MIB_NETWORK_JOINED: {
            IsLoRaMacNetworkJoined = mibSet->Param.IsNetworkJoined;
            break;
        }
        case MIB_ADR: {
            AdrCtrlOn = mibSet->Param.AdrEnable;
            break;
        }
        case MIB_NET_ID: {
            LoRaMacNetID = mibSet->Param.NetID;
            break;
        }
        case MIB_DEV_ADDR: {
            LoRaMacDevAddr = mibSet->Param.DevAddr;
            break;
        }
        case MIB_NWK_SKEY: {
            if (mibSet->Param.NwkSKey != NULL) {
                memcpy1(LoRaMacNwkSKey, mibSet->Param.NwkSKey, sizeof(LoRaMacNwkSKey));
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_APP_SKEY: {
            if (mibSet->Param.AppSKey != NULL) {
                memcpy1(LoRaMacAppSKey, mibSet->Param.AppSKey, sizeof(LoRaMacAppSKey));
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_PUBLIC_NETWORK: {
            PublicNetwork = mibSet->Param.EnablePublicNetwork;
            Radio.SetPublicNetwork(mibSet->Param.EnablePublicNetwork);
            break;
        }
        case MIB_REPEATER_SUPPORT: {
            LoRaMacParams.RepeaterSupport = mibSet->Param.EnableRepeaterSupport;
            break;
        }
        case MIB_RX2_CHANNEL: {
            verify.DatarateParams.Datarate = mibSet->Param.Rx2Channel.Datarate;
            verify.DatarateParams.DownlinkDwellTime = LoRaMacParams.DownlinkDwellTime;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_RX_DR) == true) {
                memcpy(&LoRaMacParams.Rx2Channel, &mibSet->Param.Rx2Channel, sizeof(LoRaMacParams.Rx2Channel));
                if ((LoRaMacDeviceClass == CLASS_C) && (IsLoRaMacNetworkJoined == true)) {
                    // Compute Rx2 windows parameters
                    RegionComputeRxWindowParameters(LoRaMacRegion, LoRaMacParams.Rx2Channel.Datarate,
                                                    LoRaMacParams.MinRxSymbols, LoRaMacParams.SystemMaxRxError,
                                                    &RxWindow2Config);

                    RxWindow2Config.Channel = Channel;
                    RxWindow2Config.Frequency = LoRaMacParams.Rx2Channel.Frequency;
                    RxWindow2Config.DownlinkDwellTime = LoRaMacParams.DownlinkDwellTime;
                    RxWindow2Config.RepeaterSupport = LoRaMacParams.RepeaterSupport;
                    RxWindow2Config.RxSlot = RX_SLOT_WIN_2;
                    RxWindow2Config.RxContinuous = true;

                    Radio.Sleep();
                    if (RegionRxConfig(LoRaMacRegion, &RxWindow2Config, (int8_t*)&McpsIndication.RxDatarate) == true) {
                        RxWindowSetup(RxWindow2Config.RxContinuous, LoRaMacParams.MaxRxWindow);
                        RxSlot = RxWindow2Config.RxSlot;
                    } else {
                        status = LORAMAC_STATUS_PARAMETER_INVALID;
                    }
                }
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_RX2_DEFAULT_CHANNEL: {
            verify.DatarateParams.Datarate = mibSet->Param.Rx2Channel.Datarate;
            verify.DatarateParams.DownlinkDwellTime = LoRaMacParams.DownlinkDwellTime;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_RX_DR) == true) {
                LoRaMacParamsDefaults.Rx2Channel = mibSet->Param.Rx2DefaultChannel;
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_DEFAULT_MASK: {
            chanMaskSet.ChannelsMaskIn = mibSet->Param.ChannelsMask;
            chanMaskSet.ChannelsMaskType = CHANNELS_DEFAULT_MASK;

            if (RegionChanMaskSet(LoRaMacRegion, &chanMaskSet) == false) {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_MASK: {
            chanMaskSet.ChannelsMaskIn = mibSet->Param.ChannelsMask;
            chanMaskSet.ChannelsMaskType = CHANNELS_MASK;

            if (RegionChanMaskSet(LoRaMacRegion, &chanMaskSet) == false) {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_NB_REP: {
            if ((mibSet->Param.ChannelNbRep >= 1) && (mibSet->Param.ChannelNbRep <= 15)) {
                LoRaMacParams.ChannelsNbRep = mibSet->Param.ChannelNbRep;
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_MAX_RX_WINDOW_DURATION: {
            LoRaMacParams.MaxRxWindow = mibSet->Param.MaxRxWindow;
            break;
        }
        case MIB_RECEIVE_DELAY_1: {
            LoRaMacParams.ReceiveDelay1 = mibSet->Param.ReceiveDelay1;
            break;
        }
        case MIB_RECEIVE_DELAY_2: {
            LoRaMacParams.ReceiveDelay2 = mibSet->Param.ReceiveDelay2;
            break;
        }
        case MIB_JOIN_ACCEPT_DELAY_1: {
            LoRaMacParams.JoinAcceptDelay1 = mibSet->Param.JoinAcceptDelay1;
            break;
        }
        case MIB_JOIN_ACCEPT_DELAY_2: {
            LoRaMacParams.JoinAcceptDelay2 = mibSet->Param.JoinAcceptDelay2;
            break;
        }
        case MIB_CHANNELS_DEFAULT_DATARATE: {
            verify.DatarateParams.Datarate = mibSet->Param.ChannelsDefaultDatarate;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_DEF_TX_DR) == true) {
                LoRaMacParamsDefaults.ChannelsDatarate = verify.DatarateParams.Datarate;
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_DATARATE: {
            verify.DatarateParams.Datarate = mibSet->Param.ChannelsDatarate;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_TX_DR) == true) {
                LoRaMacParams.ChannelsDatarate = verify.DatarateParams.Datarate;
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_DEFAULT_TX_POWER: {
            verify.TxPower = mibSet->Param.ChannelsDefaultTxPower;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_DEF_TX_POWER) == true) {
                LoRaMacParamsDefaults.ChannelsTxPower = verify.TxPower;
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_TX_POWER: {
            verify.TxPower = mibSet->Param.ChannelsTxPower;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_TX_POWER) == true) {
                LoRaMacParams.ChannelsTxPower = verify.TxPower;
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_UPLINK_COUNTER: {
            UpLinkCounter = mibSet->Param.UpLinkCounter;
            break;
        }
        case MIB_DOWNLINK_COUNTER: {
            DownLinkCounter = mibSet->Param.DownLinkCounter;
            break;
        }
        case MIB_SYSTEM_MAX_RX_ERROR: {
            LoRaMacParams.SystemMaxRxError = LoRaMacParamsDefaults.SystemMaxRxError = mibSet->Param.SystemMaxRxError;
            break;
        }
        case MIB_MIN_RX_SYMBOLS: {
            LoRaMacParams.MinRxSymbols = LoRaMacParamsDefaults.MinRxSymbols = mibSet->Param.MinRxSymbols;
            break;
        }
        case MIB_ANTENNA_GAIN: {
            LoRaMacParams.AntennaGain = mibSet->Param.AntennaGain;
            break;
        }
        case MIB_MULTICAST_CHANNEL: {
            status = LoRaMacMulticastChannelLink(mibSet->Param.MulticastList);
            break;
        }
        case MIB_MULTICAST_CHANNEL_DEL: {
            status = LoRaMacMulticastChannelUnlink(mibSet->Param.MulticastList);
            break;
        }
#ifdef CONFIG_LWAN
        case MIB_RX1_DATARATE_OFFSET: {
            LoRaMacParams.Rx1DrOffset = mibSet->Param.Rx1DrOffset;
            break;
        }
#endif

#ifdef CONFIG_LINKWAN
        case MIB_FREQ_BAND: {
            LoRaMacParams.freqband = mibSet->Param.freqband;
            break;
        }
#endif
        default: {
            if (LoRaMacDeviceClass == CLASS_B) {
                status = LoRaMacMibClassBSetRequestConfirm(mibSet);
            } else {
                status = LORAMAC_STATUS_SERVICE_UNKNOWN;
            }
            break;
        }
    }

    return status;
}

LoRaMacStatus_t LoRaMacChannelAdd(uint8_t id, ChannelParams_t params)
{
    ChannelAddParams_t channelAdd;

    // Validate if the MAC is in a correct state
    if ((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) {
        if ((LoRaMacState & LORAMAC_TX_CONFIG) != LORAMAC_TX_CONFIG) {
            return LORAMAC_STATUS_BUSY;
        }
    }

    channelAdd.NewChannel = &params;
    channelAdd.ChannelId = id;

    return RegionChannelAdd(LoRaMacRegion, &channelAdd);
}

LoRaMacStatus_t LoRaMacChannelRemove(uint8_t id)
{
    ChannelRemoveParams_t channelRemove;

    if ((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) {
        if ((LoRaMacState & LORAMAC_TX_CONFIG) != LORAMAC_TX_CONFIG) {
            return LORAMAC_STATUS_BUSY;
        }
    }

    channelRemove.ChannelId = id;

    if (RegionChannelsRemove(LoRaMacRegion, &channelRemove) == false) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacMulticastChannelLink(MulticastParams_t* channelParam)
{
    if (channelParam == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    if ((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) {
        return LORAMAC_STATUS_BUSY;
    }

    // Calculate class b parameters
    LoRaMacClassBSetMulticastPeriodicity(channelParam);

    // Reset downlink counter
    channelParam->DownLinkCounter = 0;
    channelParam->Next = NULL;

    if (MulticastChannels == NULL) {
        // New node is the fist element
        MulticastChannels = channelParam;
    } else {
        MulticastParams_t* cur = MulticastChannels;

        // Search the last node in the list
        while (cur->Next != NULL) {
            cur = cur->Next;
        }
        // This function always finds the last node
        cur->Next = channelParam;
    }

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacMulticastChannelUnlink(MulticastParams_t* channelParam)
{
    if (channelParam == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    if ((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) {
        return LORAMAC_STATUS_BUSY;
    }

    if (MulticastChannels != NULL) {
        if (MulticastChannels == channelParam) {
            // First element
            MulticastChannels = channelParam->Next;
        } else {
            MulticastParams_t* cur = MulticastChannels;

            // Search the node in the list
            while (cur->Next && cur->Next != channelParam) {
                cur = cur->Next;
            }
            // If we found the node, remove it
            if (cur->Next) {
                cur->Next = channelParam->Next;
            }
        }
        channelParam->Next = NULL;
    }

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacMlmeRequest(MlmeReq_t* mlmeRequest)
{
    LoRaMacStatus_t status = LORAMAC_STATUS_SERVICE_UNKNOWN;
    LoRaMacHeader_t macHdr;
    MlmeConfirmQueue_t queueElement;
    AlternateDrParams_t altDr;
    // VerifyParams_t verify;
    // GetPhyParams_t getPhy;
    // PhyParam_t phyParam;

    if (mlmeRequest == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    if (LoRaMacState != LORAMAC_IDLE) {
        return LORAMAC_STATUS_BUSY;
    }
    if (LoRaMacConfirmQueueIsFull() == true) {
        return LORAMAC_STATUS_BUSY;
    }

    switch (mlmeRequest->Type) {
        case MLME_JOIN: {
            if ((mlmeRequest->Req.Join.DevEui == NULL) || (mlmeRequest->Req.Join.AppEui == NULL) ||
                (mlmeRequest->Req.Join.AppKey == NULL) || (mlmeRequest->Req.Join.NbTrials == 0)) {
                return LORAMAC_STATUS_PARAMETER_INVALID;
            }
#if 0
            // Verify the parameter NbTrials for the join procedure
            verify.NbJoinTrials = mlmeRequest->Req.Join.NbTrials;


            if ( RegionVerify( LoRaMacRegion, &verify, PHY_NB_JOIN_TRIALS ) == false ) {
                // Value not supported, get default
                getPhy.Attribute = PHY_DEF_NB_JOIN_TRIALS;
                phyParam = RegionGetPhyParam( LoRaMacRegion, &getPhy );
                mlmeRequest->Req.Join.NbTrials = ( uint8_t ) phyParam.Value;
            }
#endif

            LoRaMacFlags.Bits.MlmeReq = 1;
            queueElement.Request = mlmeRequest->Type;

            LoRaMacDevEui = mlmeRequest->Req.Join.DevEui;
            LoRaMacAppEui = mlmeRequest->Req.Join.AppEui;
            LoRaMacAppKey = mlmeRequest->Req.Join.AppKey;
            queueElement.Status = LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL;
            queueElement.RestrictCommonReadyToHandle = false;
            LoRaMacConfirmQueueAdd(&queueElement);
            MaxJoinRequestTrials = mlmeRequest->Req.Join.NbTrials;

#ifdef CONFIG_LINKWAN
            // Reset variable JoinRequestTrials
            JoinRequestTrials = 0;
#endif

            // Setup header information
            macHdr.Value = 0;
            macHdr.Bits.MType = FRAME_TYPE_JOIN_REQ;

            ResetMacParameters();

            altDr.NbTrials = JoinRequestTrials + 1;
#ifdef CONFIG_LINKWAN
            altDr.joinmethod = mlmeRequest->Req.Join.method;
            altDr.datarate = mlmeRequest->Req.Join.datarate;
            LoRaMacParams.method = altDr.joinmethod;
            LoRaMacParams.freqband = mlmeRequest->Req.Join.freqband;
            LoRaMacParams.update_freqband = true;
#endif
            LoRaMacParams.ChannelsDatarate = RegionAlternateDr(LoRaMacRegion, &altDr);
            LOG_PRINTF(LL_VDEBUG, "MacHdr major:%d rfu:%d mtype:%d\r\n", macHdr.Bits.Major, macHdr.Bits.RFU,
                       macHdr.Bits.MType);

            status = Send(&macHdr, 0, NULL, 0);
            break;
        }
        case MLME_LINK_CHECK: {
            // Apply the request
            LoRaMacFlags.Bits.MlmeReq = 1;
            queueElement.Request = mlmeRequest->Type;
            queueElement.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
            queueElement.RestrictCommonReadyToHandle = false;
            LoRaMacConfirmQueueAdd(&queueElement);

            // LoRaMac will send this command piggy-pack
            status = AddMacCommand(MOTE_MAC_LINK_CHECK_REQ, 0, 0);
            break;
        }
        case MLME_TXCW: {
            // Apply the request
            LoRaMacFlags.Bits.MlmeReq = 1;
            queueElement.Request = mlmeRequest->Type;
            queueElement.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
            queueElement.RestrictCommonReadyToHandle = false;
            LoRaMacConfirmQueueAdd(&queueElement);

            status = SetTxContinuousWave(mlmeRequest->Req.TxCw.Timeout);
            break;
        }
        case MLME_TXCW_1: {
            // Apply the request
            LoRaMacFlags.Bits.MlmeReq = 1;
            queueElement.Request = mlmeRequest->Type;
            queueElement.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
            queueElement.RestrictCommonReadyToHandle = false;
            LoRaMacConfirmQueueAdd(&queueElement);

            status = SetTxContinuousWave1(mlmeRequest->Req.TxCw.Timeout, mlmeRequest->Req.TxCw.Frequency,
                                          mlmeRequest->Req.TxCw.Power);
            break;
        }
        case MLME_DEVICE_TIME: {
            // Apply the request
            LoRaMacFlags.Bits.MlmeReq = 1;
            queueElement.Request = mlmeRequest->Type;
            queueElement.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
            queueElement.RestrictCommonReadyToHandle = false;
            LoRaMacConfirmQueueAdd(&queueElement);

            // LoRaMac will send this command piggy-pack
            status = AddMacCommand(MOTE_MAC_DEVICE_TIME_REQ, 0, 0);
            break;
        }
        case MLME_PING_SLOT_INFO: {
            uint8_t value = mlmeRequest->Req.PingSlotInfo.PingSlot.Value;

            // Apply the request
            LoRaMacFlags.Bits.MlmeReq = 1;
            queueElement.Request = mlmeRequest->Type;
            queueElement.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
            queueElement.RestrictCommonReadyToHandle = false;
            LoRaMacConfirmQueueAdd(&queueElement);

            // LoRaMac will send this command piggy-pack
            LoRaMacClassBSetPingSlotInfo(mlmeRequest->Req.PingSlotInfo.PingSlot.Fields.Periodicity);

            status = AddMacCommand(MOTE_MAC_PING_SLOT_INFO_REQ, value, 0);
            break;
        }
        case MLME_BEACON_TIMING: {
            // Apply the request
            LoRaMacFlags.Bits.MlmeReq = 1;
            queueElement.Request = mlmeRequest->Type;
            queueElement.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
            queueElement.RestrictCommonReadyToHandle = false;
            LoRaMacConfirmQueueAdd(&queueElement);

            // LoRaMac will send this command piggy-pack
            status = AddMacCommand(MOTE_MAC_BEACON_TIMING_REQ, 0, 0);
            break;
        }
        case MLME_BEACON_ACQUISITION: {
            // Apply the request
            LoRaMacFlags.Bits.MlmeReq = 1;
            queueElement.Request = mlmeRequest->Type;
            queueElement.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
            queueElement.RestrictCommonReadyToHandle = true;
            LoRaMacConfirmQueueAdd(&queueElement);

            if (LoRaMacClassBIsAcquisitionInProgress() == false) {
                // Start class B algorithm
                LoRaMacClassBSetBeaconState(BEACON_STATE_ACQUISITION);
                LoRaMacClassBBeaconTimerEvent();

                status = LORAMAC_STATUS_OK;
            } else {
                status = LORAMAC_STATUS_BUSY;
            }
            break;
        }
        default:
            break;
    }

    if (status != LORAMAC_STATUS_OK) {
        NodeAckRequested = false;
        LoRaMacConfirmQueueRemoveLast();
        if (LoRaMacConfirmQueueGetCnt() == 0) {
            LoRaMacFlags.Bits.MlmeReq = 0;
        }
    }

    return status;
}

LoRaMacStatus_t LoRaMacMcpsRequest(McpsReq_t* mcpsRequest)
{
    GetPhyParams_t getPhy;
    PhyParam_t phyParam;
    LoRaMacStatus_t status = LORAMAC_STATUS_SERVICE_UNKNOWN;
    LoRaMacHeader_t macHdr;
    VerifyParams_t verify;
    uint8_t fPort = 0;
    void* fBuffer;
    uint16_t fBufferSize;
    int8_t datarate;
    bool readyToSend = false;

    if (mcpsRequest == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    if (((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) ||
        ((LoRaMacState & LORAMAC_TX_DELAYED) == LORAMAC_TX_DELAYED)) {
        return LORAMAC_STATUS_BUSY;
    }

    macHdr.Value = 0;
    memset1((uint8_t*)&McpsConfirm, 0, sizeof(McpsConfirm));
    McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;

    // AckTimeoutRetriesCounter must be reset every time a new request (unconfirmed or confirmed) is performed.
    AckTimeoutRetriesCounter = 1;

    switch (mcpsRequest->Type) {
        case MCPS_UNCONFIRMED: {
            readyToSend = true;
            AckTimeoutRetries = 1;

            macHdr.Bits.MType = FRAME_TYPE_DATA_UNCONFIRMED_UP;
            fPort = mcpsRequest->Req.Unconfirmed.fPort;
            fBuffer = mcpsRequest->Req.Unconfirmed.fBuffer;
            fBufferSize = mcpsRequest->Req.Unconfirmed.fBufferSize;
            datarate = mcpsRequest->Req.Unconfirmed.Datarate;
            break;
        }
        case MCPS_CONFIRMED: {
            readyToSend = true;
            AckTimeoutRetries = mcpsRequest->Req.Confirmed.NbTrials;

            macHdr.Bits.MType = FRAME_TYPE_DATA_CONFIRMED_UP;
            fPort = mcpsRequest->Req.Confirmed.fPort;
            fBuffer = mcpsRequest->Req.Confirmed.fBuffer;
            fBufferSize = mcpsRequest->Req.Confirmed.fBufferSize;
            datarate = mcpsRequest->Req.Confirmed.Datarate;
            break;
        }
        case MCPS_PROPRIETARY: {
            readyToSend = true;
            AckTimeoutRetries = 1;

            macHdr.Bits.MType = FRAME_TYPE_PROPRIETARY;
            fBuffer = mcpsRequest->Req.Proprietary.fBuffer;
            fBufferSize = mcpsRequest->Req.Proprietary.fBufferSize;
            datarate = mcpsRequest->Req.Proprietary.Datarate;
            break;
        }
        default:
            break;
    }

    // Filter fPorts
    if (IsFPortAllowed(fPort) == false) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    // Get the minimum possible datarate
    getPhy.Attribute = PHY_MIN_TX_DR;
    getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    // Apply the minimum possible datarate.
    // Some regions have limitations for the minimum datarate.
    datarate = MAX(datarate, phyParam.Value);

    if (readyToSend == true) {
        if (AdrCtrlOn == false) {
            verify.DatarateParams.Datarate = datarate;
            verify.DatarateParams.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_TX_DR) == true) {
                LoRaMacParams.ChannelsDatarate = verify.DatarateParams.Datarate;
            } else {
                return LORAMAC_STATUS_PARAMETER_INVALID;
            }
        }

        status = Send(&macHdr, fPort, fBuffer, fBufferSize);
        if (status == LORAMAC_STATUS_OK) {
            McpsConfirm.McpsRequest = mcpsRequest->Type;
            LoRaMacFlags.Bits.McpsReq = 1;
        } else {
            NodeAckRequested = false;
        }
    }

    return status;
}

void LoRaMacTestRxWindowsOn(bool enable)
{
    IsRxWindowsEnabled = enable;
}

void LoRaMacTestSetMic(uint16_t txPacketCounter)
{
    UpLinkCounter = txPacketCounter;
    IsUpLinkCounterFixed = true;
}

void LoRaMacTestSetDutyCycleOn(bool enable)
{
    VerifyParams_t verify;

    verify.DutyCycle = enable;

    if (RegionVerify(LoRaMacRegion, &verify, PHY_DUTY_CYCLE) == true) {
        DutyCycleOn = enable;
    }
}

void LoRaMacTestSetChannel(uint8_t channel)
{
    Channel = channel;
}

#if 0
static void SetPublicNetwork( bool enable )
{
    PublicNetwork = enable;
    Radio.SetModem( MODEM_LORA );
    if ( PublicNetwork == true ) {
        // Change LoRa modem SyncWord
        Radio.SetSyncWord( LORA_MAC_PUBLIC_SYNCWORD );
    } else {
        // Change LoRa modem SyncWord
        Radio.SetSyncWord( LORA_MAC_PRIVATE_SYNCWORD );
    }
}
#endif
